ETH Price: $2,910.68 (-0.62%)

Contract

0x57Bb1FF6376d8c0aD39cc2091D3065FdB2a28c12
 

Overview

ETH Balance

0 ETH

ETH Value

$0.00

Token Holdings

More Info

Private Name Tags

Transaction Hash
Block
From
To
Set Referrer225996132025-08-01 0:44:01178 days ago1754009041IN
0x57Bb1FF6...dB2a28c12
0 ETH00.00002558
Set Referrer225994932025-08-01 0:40:01178 days ago1754008801IN
0x57Bb1FF6...dB2a28c12
0 ETH00.00002445
Set Referral Cod...221182322025-07-20 21:17:59189 days ago1753046279IN
0x57Bb1FF6...dB2a28c12
0 ETH0.000000120.0011061
Set Referrer219350842025-07-16 15:33:03193 days ago1752679983IN
0x57Bb1FF6...dB2a28c12
0 ETH0.000000090.0010382
Set Referrer214155012025-07-04 14:53:37205 days ago1751640817IN
0x57Bb1FF6...dB2a28c12
0 ETH0.000000060.00093408
Set Referrer202127222025-06-06 18:40:59233 days ago1749235259IN
0x57Bb1FF6...dB2a28c12
0 ETH00.00000052
Set Referrer151391092025-02-09 8:00:33351 days ago1739088033IN
0x57Bb1FF6...dB2a28c12
0 ETH0.000000010.00056922
Set Referrer151391082025-02-09 8:00:31351 days ago1739088031IN
0x57Bb1FF6...dB2a28c12
0 ETH0.000000030.00056928
Set Referrer148468022025-02-02 13:36:59358 days ago1738503419IN
0x57Bb1FF6...dB2a28c12
0 ETH0.000000050.00030525
Set Referrer148174482025-02-01 21:18:31358 days ago1738444711IN
0x57Bb1FF6...dB2a28c12
0 ETH0.000000030.00028668
Set Referrer145432322025-01-26 12:57:59365 days ago1737896279IN
0x57Bb1FF6...dB2a28c12
0 ETH0.000000010.00015095
Set Referrer145432292025-01-26 12:57:53365 days ago1737896273IN
0x57Bb1FF6...dB2a28c12
0 ETH0.000000020.00015105
Set Referrer142915662025-01-20 17:09:07370 days ago1737392947IN
0x57Bb1FF6...dB2a28c12
0 ETH0.000001840.00100129
Set Referrer141097892025-01-16 12:09:53375 days ago1737029393IN
0x57Bb1FF6...dB2a28c12
0 ETH0.000006930.00000029
Set Referrer139394842025-01-12 13:33:03379 days ago1736688783IN
0x57Bb1FF6...dB2a28c12
0 ETH0.000000020.0002713
Set Referrer133963932024-12-30 23:50:01391 days ago1735602601IN
0x57Bb1FF6...dB2a28c12
0 ETH0.000002430.00013271
Set Referrer128859082024-12-19 4:13:51403 days ago1734581631IN
0x57Bb1FF6...dB2a28c12
0 ETH0.000000280.00027641
Set Referrer128104072024-12-17 10:17:09405 days ago1734430629IN
0x57Bb1FF6...dB2a28c12
0 ETH0.000000070.00188
Set Referrer128104002024-12-17 10:16:55405 days ago1734430615IN
0x57Bb1FF6...dB2a28c12
0 ETH0.000000140.00188705
Set Referrer128026862024-12-17 5:59:47405 days ago1734415187IN
0x57Bb1FF6...dB2a28c12
0 ETH0.000000040.00017749
Set Referrer126708902024-12-14 4:46:35408 days ago1734151595IN
0x57Bb1FF6...dB2a28c12
0 ETH0.000002090.00026049
Set Referrer126708882024-12-14 4:46:31408 days ago1734151591IN
0x57Bb1FF6...dB2a28c12
0 ETH0.00000210.00026198
Set Referrer126319062024-12-13 7:07:07409 days ago1734073627IN
0x57Bb1FF6...dB2a28c12
0 ETH0.000001780.00057425
Set Referrer126249212024-12-13 3:14:17409 days ago1734059657IN
0x57Bb1FF6...dB2a28c12
0 ETH0.000003110.00072133
Set Referrer125207442024-12-10 17:21:43411 days ago1733851303IN
0x57Bb1FF6...dB2a28c12
0 ETH0.000002080.00132471
View all transactions

View more zero value Internal Transactions in Advanced View mode

Advanced mode:

Cross-Chain Transactions
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
SummitReferrals

Compiler Version
v0.8.4+commit.c7e474f2

Optimization Enabled:
Yes with 999 runs

Other Settings:
default evmVersion
//
//            __               ___ ___    __            _  
//           (_  | | |\/| |\/|  |   |    (_ \    / /\  |_) 
//           __) |_| |  | |  | _|_  |    __) \/\/ /--\ |   
//

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;
pragma experimental ABIEncoderV2;

import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "./interface/ISummitReferrals.sol";
import "./interface/ISummitPoints.sol";
import "./interface/IBlast.sol";
import "./lib/Maintainable.sol";

// | Level    | Custom Benefit  | Reward  | Self Volume Required  | Referred Volume Required  | Referral Num Required   |
// | ---      | ---             | ---     | ---                   | ---                       | ---                     |
// | Wood     |                 | 0       | 0                     | 0                         | 0                       |
// | Bronze   | Can refer       | 2%      | 100                   | 0                         | 0                       |
// | Silver   |                 | 3%      | 1000                  | 10000                     | 3                       |
// | Gold     |                 | 5%      | 2000                  | 25000                     | 5                       |
// | Platinum |                 | 7%      | 5000                  | 100000                    | 10                      |
// | Noble    |                 | 10%     | 25000                 | 1000000                   | 25                      |

contract SummitReferrals is Maintainable, ISummitReferrals {
    using SafeMath for uint256;

    address public SUMMIT_POINTS;

    // Level requirements
    uint256 public levelCount = 6;
    mapping(uint256 => uint256) public LEVEL_REF_VOLUME_REQ; // = [0, 0, 10000e18, 25000e18, 100000e18, 1000000e18];
    mapping(uint256 => uint256) public LEVEL_SELF_VOLUME_REQ; // = [0, 100e18, 1000e18, 2000e18, 5000e18, 25000e18];
    mapping(uint256 => uint256) public LEVEL_REFS_REQ; // = [0, 0, 3, 5, 10, 25];

    mapping(uint256 => uint256) public LEVEL_MULT_REWARD; // = [0, 200, 400, 500, 700, 1500];

    uint256 public BONUS_FOR_BEING_REFERRED = 200;
    mapping(address => address) public REFERRER;
    mapping(address => uint256) public REF_COUNT;
    mapping(address => uint8) public REF_BOOST_LEVEL;
    mapping(string => address) public REF_CODE;
    mapping(address => string) public REF_CODE_INV;

    error InvalidCode();
    error AlreadySetCode();
    error AlreadyInitialized();
    error MissingReferral();
    error AlreadyReferredByUser();
    error SelfReferral();
    error ReciprocalReferral();
    error LengthMismatch();
    error CodeNotAvailable();
    error MustBeAtLeastBronze();
    error InvalidLevel();

    constructor() {
      uint256[6] memory refVolumeReq = [uint256(0), 0, 10000e18, 25000e18, 100000e18, 1000000e18];
      uint256[6] memory selfVolumeReq = [uint256(0), 100e18, 1000e18, 2000e18, 5000e18, 25000e18];
      uint256[6] memory refsReq = [uint256(0), 0, 3, 5, 10, 25];
      uint256[6] memory multReward = [uint256(0), 200, 400, 500, 700, 1500];

      for (uint256 i = 0; i < levelCount; i++) {
        LEVEL_REF_VOLUME_REQ[i] = refVolumeReq[i];
        LEVEL_SELF_VOLUME_REQ[i] = selfVolumeReq[i];
        LEVEL_REFS_REQ[i] = refsReq[i];
        LEVEL_MULT_REWARD[i] = multReward[i];
      }
    }

    bool public initialized = false;
    address public governor;
    function initialize(address _governor) public onlyMaintainer {
      if (initialized) revert AlreadyInitialized();
      initialized = true;

      IBlast blast = IBlast(0x4300000000000000000000000000000000000002);
      blast.configureClaimableGas();
      blast.configureGovernor(_governor);
      
      governor = _governor;
    }


    function setPointsContract(address _pointsContract) override public onlyMaintainer {
      emit UpdatedPointsContract(_pointsContract);
      SUMMIT_POINTS = _pointsContract;
    }

    function stringEquals(string memory s1, string memory s2) internal pure returns(bool) {
        return keccak256(abi.encode(s1)) == keccak256(abi.encode(s2));
    }

    function setReferrer(address _referrer, string memory _code) override public {
      // Get referrer from code or argument
      address referrer = _referrer == address(0) ? REF_CODE[_code] : _referrer;

      // Checks
      if (referrer == address(0)) revert MissingReferral();
      if (referrer == msg.sender) revert SelfReferral();
      if (referrer == REFERRER[msg.sender]) revert AlreadyReferredByUser();
      if (REFERRER[referrer] == msg.sender) revert ReciprocalReferral();
      if (REFERRER[REFERRER[referrer]] == msg.sender) revert ReciprocalReferral();

      // Validate referrer is at least bronze level
      if (getReferrerLevel(referrer) == 0) revert MustBeAtLeastBronze();

      // Remove from prev referrer count
      if (REFERRER[msg.sender] != address(0) && REF_COUNT[REFERRER[msg.sender]] >= 1) {
        REF_COUNT[REFERRER[msg.sender]] -= 1;
      }

      REFERRER[msg.sender] = referrer;
      REF_COUNT[referrer] += 1;

      emit UpdatedReferrer(msg.sender, referrer);
    }

    function setReferralCode(string memory _code) override public {
      if(bytes(_code).length < 3 || bytes(_code).length > 15) revert InvalidCode();

      // Validate user doesnt already have code
      if (bytes(REF_CODE_INV[msg.sender]).length != 0) revert AlreadySetCode();

      // Validate referrer is at least bronze level
      if (getReferrerLevel(msg.sender) == 0) revert MustBeAtLeastBronze();

      // If code is already being used
      if (REF_CODE[_code] != address(0)) revert CodeNotAvailable();

      REF_CODE[REF_CODE_INV[msg.sender]] = address(0);
      REF_CODE_INV[msg.sender] = _code;
      REF_CODE[_code] = msg.sender;

      emit UpdatedReferralCode(msg.sender, msg.sender, _code);
    }

    function maintainerSetReferralCode(address _add, string memory _code) override public onlyMaintainer {
      // If code is already being used
      if (REF_CODE[_code] != address(0)) revert CodeNotAvailable();

      REF_CODE[REF_CODE_INV[_add]] = address(0);
      REF_CODE_INV[_add] = _code;
      REF_CODE[_code] = _add;

      emit UpdatedReferralCode(msg.sender, _add, _code);
    }

    function boostReferrer(address _referrer, uint8 _boostLevel) override public onlyMaintainer {
      REF_BOOST_LEVEL[_referrer] = _boostLevel;
      emit BoostedReferrer(_referrer, _boostLevel);
    }

    function boostReferrers(address[] memory _referrers, uint8[] memory _levels) override public onlyMaintainer {
      if (_referrers.length != _levels.length) revert LengthMismatch();
      for (uint256 i = 0; i < _referrers.length; i++) {
        if (REF_BOOST_LEVEL[_referrers[i]] != _levels[i]) {
          REF_BOOST_LEVEL[_referrers[i]] = _levels[i];
        }
      }
      emit BoostedReferrers(_referrers, _levels);
    }

    function setLevelData(uint256[] memory _refVolumeReq, uint256[] memory _selfVolumeReq, uint256[] memory _refsReq, uint256[] memory _multReward, uint256 _hasReferrerBonusMult) override public onlyMaintainer {
      if (_refVolumeReq.length != _selfVolumeReq.length || _refVolumeReq.length != _refsReq.length || _refVolumeReq.length != _multReward.length) revert LengthMismatch();

      uint256 newLength = _refVolumeReq.length;

      for (uint256 i = 0; i < newLength; i++) {
        LEVEL_REF_VOLUME_REQ[i] = _refVolumeReq[i];
        LEVEL_SELF_VOLUME_REQ[i] = _selfVolumeReq[i];
        LEVEL_REFS_REQ[i] = _refsReq[i];
        LEVEL_MULT_REWARD[i] = _multReward[i];
      }

      levelCount = newLength;

      BONUS_FOR_BEING_REFERRED = _hasReferrerBonusMult;
      emit UpdatedLevelData(_refVolumeReq, _selfVolumeReq, _refsReq, _multReward, _hasReferrerBonusMult);
    }

    function getReferrer(address _add) override public view returns (address) {
      return REFERRER[_add];
    }

    function getReferrerCode(address _add) override public view returns (string memory) {
      return REF_CODE_INV[REFERRER[_add]];
    }

    function getReferrerLevelWithoutBoost(address _add) override public view returns (uint8) {
      if (SUMMIT_POINTS == address(0)) return 0;

      (uint256 _selfVolume, uint256 _refVolume,) = ISummitPoints(SUMMIT_POINTS).getVolume(_add);

      for (uint8 i = 0; i < levelCount; i++) {
        if (_selfVolume < LEVEL_SELF_VOLUME_REQ[i + 1]) return i;
        if (_refVolume < LEVEL_REF_VOLUME_REQ[i + 1]) return i;
        if (REF_COUNT[_add] < LEVEL_REFS_REQ[i + 1]) return i;
      }

      return uint8(levelCount);
    }

    function getReferrerLevel(address _add) override public view returns (uint8) {
      if (REF_BOOST_LEVEL[_add] > 0) {
        return REF_BOOST_LEVEL[_add] > (levelCount - 1) ? uint8(levelCount - 1) : REF_BOOST_LEVEL[_add];
      }
      return getReferrerLevelWithoutBoost(_add);
    }

    function getLevelRequirements(uint8 _level) override public view returns (uint256 selfVolume, uint256 refVolume, uint256 refsCount) {
      if (_level >= levelCount) revert InvalidLevel();
      return (
        LEVEL_SELF_VOLUME_REQ[_level],
        LEVEL_REF_VOLUME_REQ[_level],
        LEVEL_REFS_REQ[_level]
      );
    }

    function getUserNextLevelRequirements(address _add) override public view returns (uint256 selfVolume, uint256 refVolume, uint256 refsCount) {
      uint8 nextLevel = getReferrerLevelWithoutBoost(_add) + 1;
      uint8 level = nextLevel >= (levelCount - 1) ? uint8(levelCount - 1) : nextLevel;
      return getLevelRequirements(level);
    }

    function getRefsCount(address _add) override public view returns (uint256) {
      return REF_COUNT[_add];
    }

    function getRefVolumeBonusMultiplier(address _add) override public view returns (uint256) {
      return LEVEL_MULT_REWARD[getReferrerLevel(_add)];
    }

    function getSelfVolumeMultiplier(address _add) override public view returns (uint256) {
      return 10000 + (REFERRER[_add] != address(0) ? BONUS_FOR_BEING_REFERRED : 0);
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/AccessControl.sol)

pragma solidity ^0.8.0;

import "./IAccessControl.sol";
import "../utils/Context.sol";
import "../utils/Strings.sol";
import "../utils/introspection/ERC165.sol";

/**
 * @dev Contract module that allows children to implement role-based access
 * control mechanisms. This is a lightweight version that doesn't allow enumerating role
 * members except through off-chain means by accessing the contract event logs. Some
 * applications may benefit from on-chain enumerability, for those cases see
 * {AccessControlEnumerable}.
 *
 * Roles are referred to by their `bytes32` identifier. These should be exposed
 * in the external API and be unique. The best way to achieve this is by
 * using `public constant` hash digests:
 *
 * ```solidity
 * bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
 * ```
 *
 * Roles can be used to represent a set of permissions. To restrict access to a
 * function call, use {hasRole}:
 *
 * ```solidity
 * function foo() public {
 *     require(hasRole(MY_ROLE, msg.sender));
 *     ...
 * }
 * ```
 *
 * Roles can be granted and revoked dynamically via the {grantRole} and
 * {revokeRole} functions. Each role has an associated admin role, and only
 * accounts that have a role's admin role can call {grantRole} and {revokeRole}.
 *
 * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
 * that only accounts with this role will be able to grant or revoke other
 * roles. More complex role relationships can be created by using
 * {_setRoleAdmin}.
 *
 * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
 * grant and revoke this role. Extra precautions should be taken to secure
 * accounts that have been granted it. We recommend using {AccessControlDefaultAdminRules}
 * to enforce additional security measures for this role.
 */
abstract contract AccessControl is Context, IAccessControl, ERC165 {
    struct RoleData {
        mapping(address => bool) members;
        bytes32 adminRole;
    }

    mapping(bytes32 => RoleData) private _roles;

    bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;

    /**
     * @dev Modifier that checks that an account has a specific role. Reverts
     * with a standardized message including the required role.
     *
     * The format of the revert reason is given by the following regular expression:
     *
     *  /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
     *
     * _Available since v4.1._
     */
    modifier onlyRole(bytes32 role) {
        _checkRole(role);
        _;
    }

    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);
    }

    /**
     * @dev Returns `true` if `account` has been granted `role`.
     */
    function hasRole(bytes32 role, address account) public view virtual override returns (bool) {
        return _roles[role].members[account];
    }

    /**
     * @dev Revert with a standard message if `_msgSender()` is missing `role`.
     * Overriding this function changes the behavior of the {onlyRole} modifier.
     *
     * Format of the revert message is described in {_checkRole}.
     *
     * _Available since v4.6._
     */
    function _checkRole(bytes32 role) internal view virtual {
        _checkRole(role, _msgSender());
    }

    /**
     * @dev Revert with a standard message if `account` is missing `role`.
     *
     * The format of the revert reason is given by the following regular expression:
     *
     *  /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
     */
    function _checkRole(bytes32 role, address account) internal view virtual {
        if (!hasRole(role, account)) {
            revert(
                string(
                    abi.encodePacked(
                        "AccessControl: account ",
                        Strings.toHexString(account),
                        " is missing role ",
                        Strings.toHexString(uint256(role), 32)
                    )
                )
            );
        }
    }

    /**
     * @dev Returns the admin role that controls `role`. See {grantRole} and
     * {revokeRole}.
     *
     * To change a role's admin, use {_setRoleAdmin}.
     */
    function getRoleAdmin(bytes32 role) public view virtual override returns (bytes32) {
        return _roles[role].adminRole;
    }

    /**
     * @dev Grants `role` to `account`.
     *
     * If `account` had not been already granted `role`, emits a {RoleGranted}
     * event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     *
     * May emit a {RoleGranted} event.
     */
    function grantRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
        _grantRole(role, account);
    }

    /**
     * @dev Revokes `role` from `account`.
     *
     * If `account` had been granted `role`, emits a {RoleRevoked} event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     *
     * May emit a {RoleRevoked} event.
     */
    function revokeRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
        _revokeRole(role, account);
    }

    /**
     * @dev Revokes `role` from the calling account.
     *
     * Roles are often managed via {grantRole} and {revokeRole}: this function's
     * purpose is to provide a mechanism for accounts to lose their privileges
     * if they are compromised (such as when a trusted device is misplaced).
     *
     * If the calling account had been revoked `role`, emits a {RoleRevoked}
     * event.
     *
     * Requirements:
     *
     * - the caller must be `account`.
     *
     * May emit a {RoleRevoked} event.
     */
    function renounceRole(bytes32 role, address account) public virtual override {
        require(account == _msgSender(), "AccessControl: can only renounce roles for self");

        _revokeRole(role, account);
    }

    /**
     * @dev Grants `role` to `account`.
     *
     * If `account` had not been already granted `role`, emits a {RoleGranted}
     * event. Note that unlike {grantRole}, this function doesn't perform any
     * checks on the calling account.
     *
     * May emit a {RoleGranted} event.
     *
     * [WARNING]
     * ====
     * This function should only be called from the constructor when setting
     * up the initial roles for the system.
     *
     * Using this function in any other way is effectively circumventing the admin
     * system imposed by {AccessControl}.
     * ====
     *
     * NOTE: This function is deprecated in favor of {_grantRole}.
     */
    function _setupRole(bytes32 role, address account) internal virtual {
        _grantRole(role, account);
    }

    /**
     * @dev Sets `adminRole` as ``role``'s admin role.
     *
     * Emits a {RoleAdminChanged} event.
     */
    function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
        bytes32 previousAdminRole = getRoleAdmin(role);
        _roles[role].adminRole = adminRole;
        emit RoleAdminChanged(role, previousAdminRole, adminRole);
    }

    /**
     * @dev Grants `role` to `account`.
     *
     * Internal function without access restriction.
     *
     * May emit a {RoleGranted} event.
     */
    function _grantRole(bytes32 role, address account) internal virtual {
        if (!hasRole(role, account)) {
            _roles[role].members[account] = true;
            emit RoleGranted(role, account, _msgSender());
        }
    }

    /**
     * @dev Revokes `role` from `account`.
     *
     * Internal function without access restriction.
     *
     * May emit a {RoleRevoked} event.
     */
    function _revokeRole(bytes32 role, address account) internal virtual {
        if (hasRole(role, account)) {
            _roles[role].members[account] = false;
            emit RoleRevoked(role, account, _msgSender());
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)

pragma solidity ^0.8.0;

/**
 * @dev External interface of AccessControl declared to support ERC165 detection.
 */
interface IAccessControl {
    /**
     * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
     *
     * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
     * {RoleAdminChanged} not being emitted signaling this.
     *
     * _Available since v3.1._
     */
    event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);

    /**
     * @dev Emitted when `account` is granted `role`.
     *
     * `sender` is the account that originated the contract call, an admin role
     * bearer except when using {AccessControl-_setupRole}.
     */
    event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);

    /**
     * @dev Emitted when `account` is revoked `role`.
     *
     * `sender` is the account that originated the contract call:
     *   - if using `revokeRole`, it is the admin role bearer
     *   - if using `renounceRole`, it is the role bearer (i.e. `account`)
     */
    event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);

    /**
     * @dev Returns `true` if `account` has been granted `role`.
     */
    function hasRole(bytes32 role, address account) external view returns (bool);

    /**
     * @dev Returns the admin role that controls `role`. See {grantRole} and
     * {revokeRole}.
     *
     * To change a role's admin, use {AccessControl-_setRoleAdmin}.
     */
    function getRoleAdmin(bytes32 role) external view returns (bytes32);

    /**
     * @dev Grants `role` to `account`.
     *
     * If `account` had not been already granted `role`, emits a {RoleGranted}
     * event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     */
    function grantRole(bytes32 role, address account) external;

    /**
     * @dev Revokes `role` from `account`.
     *
     * If `account` had been granted `role`, emits a {RoleRevoked} event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     */
    function revokeRole(bytes32 role, address account) external;

    /**
     * @dev Revokes `role` from the calling account.
     *
     * Roles are often managed via {grantRole} and {revokeRole}: this function's
     * purpose is to provide a mechanism for accounts to lose their privileges
     * if they are compromised (such as when a trusted device is misplaced).
     *
     * If the calling account had been granted `role`, emits a {RoleRevoked}
     * event.
     *
     * Requirements:
     *
     * - the caller must be `account`.
     */
    function renounceRole(bytes32 role, address account) external;
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.4) (utils/Context.sol)

pragma solidity ^0.8.0;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }

    function _contextSuffixLength() internal view virtual returns (uint256) {
        return 0;
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)

pragma solidity ^0.8.0;

import "./IERC165.sol";

/**
 * @dev Implementation of the {IERC165} interface.
 *
 * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
 * for the additional interface id that will be supported. For example:
 *
 * ```solidity
 * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
 *     return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
 * }
 * ```
 *
 * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.
 */
abstract contract ERC165 is IERC165 {
    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        return interfaceId == type(IERC165).interfaceId;
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[EIP].
 *
 * Implementers can declare support of contract interfaces, which can then be
 * queried by others ({ERC165Checker}).
 *
 * For an implementation, see {ERC165}.
 */
interface IERC165 {
    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
     * to learn more about how these ids are created.
     *
     * This function call must use less than 30 000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/math/Math.sol)

pragma solidity ^0.8.0;

/**
 * @dev Standard math utilities missing in the Solidity language.
 */
library Math {
    enum Rounding {
        Down, // Toward negative infinity
        Up, // Toward infinity
        Zero // Toward zero
    }

    /**
     * @dev Returns the largest of two numbers.
     */
    function max(uint256 a, uint256 b) internal pure returns (uint256) {
        return a > b ? a : b;
    }

    /**
     * @dev Returns the smallest of two numbers.
     */
    function min(uint256 a, uint256 b) internal pure returns (uint256) {
        return a < b ? a : b;
    }

    /**
     * @dev Returns the average of two numbers. The result is rounded towards
     * zero.
     */
    function average(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b) / 2 can overflow.
        return (a & b) + (a ^ b) / 2;
    }

    /**
     * @dev Returns the ceiling of the division of two numbers.
     *
     * This differs from standard division with `/` in that it rounds up instead
     * of rounding down.
     */
    function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b - 1) / b can overflow on addition, so we distribute.
        return a == 0 ? 0 : (a - 1) / b + 1;
    }

    /**
     * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
     * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
     * with further edits by Uniswap Labs also under MIT license.
     */
    function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
        unchecked {
            // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
            // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
            // variables such that product = prod1 * 2^256 + prod0.
            uint256 prod0; // Least significant 256 bits of the product
            uint256 prod1; // Most significant 256 bits of the product
            assembly {
                let mm := mulmod(x, y, not(0))
                prod0 := mul(x, y)
                prod1 := sub(sub(mm, prod0), lt(mm, prod0))
            }

            // Handle non-overflow cases, 256 by 256 division.
            if (prod1 == 0) {
                // Solidity will revert if denominator == 0, unlike the div opcode on its own.
                // The surrounding unchecked block does not change this fact.
                // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
                return prod0 / denominator;
            }

            // Make sure the result is less than 2^256. Also prevents denominator == 0.
            require(denominator > prod1, "Math: mulDiv overflow");

            ///////////////////////////////////////////////
            // 512 by 256 division.
            ///////////////////////////////////////////////

            // Make division exact by subtracting the remainder from [prod1 prod0].
            uint256 remainder;
            assembly {
                // Compute remainder using mulmod.
                remainder := mulmod(x, y, denominator)

                // Subtract 256 bit number from 512 bit number.
                prod1 := sub(prod1, gt(remainder, prod0))
                prod0 := sub(prod0, remainder)
            }

            // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
            // See https://cs.stackexchange.com/q/138556/92363.

            // Does not overflow because the denominator cannot be zero at this stage in the function.
            uint256 twos = denominator & (~denominator + 1);
            assembly {
                // Divide denominator by twos.
                denominator := div(denominator, twos)

                // Divide [prod1 prod0] by twos.
                prod0 := div(prod0, twos)

                // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
                twos := add(div(sub(0, twos), twos), 1)
            }

            // Shift in bits from prod1 into prod0.
            prod0 |= prod1 * twos;

            // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
            // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
            // four bits. That is, denominator * inv = 1 mod 2^4.
            uint256 inverse = (3 * denominator) ^ 2;

            // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
            // in modular arithmetic, doubling the correct bits in each step.
            inverse *= 2 - denominator * inverse; // inverse mod 2^8
            inverse *= 2 - denominator * inverse; // inverse mod 2^16
            inverse *= 2 - denominator * inverse; // inverse mod 2^32
            inverse *= 2 - denominator * inverse; // inverse mod 2^64
            inverse *= 2 - denominator * inverse; // inverse mod 2^128
            inverse *= 2 - denominator * inverse; // inverse mod 2^256

            // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
            // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
            // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
            // is no longer required.
            result = prod0 * inverse;
            return result;
        }
    }

    /**
     * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
     */
    function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
        uint256 result = mulDiv(x, y, denominator);
        if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
            result += 1;
        }
        return result;
    }

    /**
     * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
     *
     * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
     */
    function sqrt(uint256 a) internal pure returns (uint256) {
        if (a == 0) {
            return 0;
        }

        // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
        //
        // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
        // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
        //
        // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
        // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
        // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
        //
        // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
        uint256 result = 1 << (log2(a) >> 1);

        // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
        // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
        // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
        // into the expected uint128 result.
        unchecked {
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            return min(result, a / result);
        }
    }

    /**
     * @notice Calculates sqrt(a), following the selected rounding direction.
     */
    function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = sqrt(a);
            return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 2, rounded down, of a positive value.
     * Returns 0 if given 0.
     */
    function log2(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 128;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 64;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 32;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 16;
            }
            if (value >> 8 > 0) {
                value >>= 8;
                result += 8;
            }
            if (value >> 4 > 0) {
                value >>= 4;
                result += 4;
            }
            if (value >> 2 > 0) {
                value >>= 2;
                result += 2;
            }
            if (value >> 1 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log2(value);
            return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 10, rounded down, of a positive value.
     * Returns 0 if given 0.
     */
    function log10(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >= 10 ** 64) {
                value /= 10 ** 64;
                result += 64;
            }
            if (value >= 10 ** 32) {
                value /= 10 ** 32;
                result += 32;
            }
            if (value >= 10 ** 16) {
                value /= 10 ** 16;
                result += 16;
            }
            if (value >= 10 ** 8) {
                value /= 10 ** 8;
                result += 8;
            }
            if (value >= 10 ** 4) {
                value /= 10 ** 4;
                result += 4;
            }
            if (value >= 10 ** 2) {
                value /= 10 ** 2;
                result += 2;
            }
            if (value >= 10 ** 1) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log10(value);
            return result + (rounding == Rounding.Up && 10 ** result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 256, rounded down, of a positive value.
     * Returns 0 if given 0.
     *
     * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
     */
    function log256(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 16;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 8;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 4;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 2;
            }
            if (value >> 8 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 256, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log256(value);
            return result + (rounding == Rounding.Up && 1 << (result << 3) < value ? 1 : 0);
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/math/SafeMath.sol)

pragma solidity ^0.8.0;

// CAUTION
// This version of SafeMath should only be used with Solidity 0.8 or later,
// because it relies on the compiler's built in overflow checks.

/**
 * @dev Wrappers over Solidity's arithmetic operations.
 *
 * NOTE: `SafeMath` is generally not needed starting with Solidity 0.8, since the compiler
 * now has built in overflow checking.
 */
library SafeMath {
    /**
     * @dev Returns the addition of two unsigned integers, with an overflow flag.
     *
     * _Available since v3.4._
     */
    function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            uint256 c = a + b;
            if (c < a) return (false, 0);
            return (true, c);
        }
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, with an overflow flag.
     *
     * _Available since v3.4._
     */
    function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b > a) return (false, 0);
            return (true, a - b);
        }
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, with an overflow flag.
     *
     * _Available since v3.4._
     */
    function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
            // benefit is lost if 'b' is also tested.
            // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
            if (a == 0) return (true, 0);
            uint256 c = a * b;
            if (c / a != b) return (false, 0);
            return (true, c);
        }
    }

    /**
     * @dev Returns the division of two unsigned integers, with a division by zero flag.
     *
     * _Available since v3.4._
     */
    function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b == 0) return (false, 0);
            return (true, a / b);
        }
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
     *
     * _Available since v3.4._
     */
    function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b == 0) return (false, 0);
            return (true, a % b);
        }
    }

    /**
     * @dev Returns the addition of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `+` operator.
     *
     * Requirements:
     *
     * - Addition cannot overflow.
     */
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        return a + b;
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting on
     * overflow (when the result is negative).
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     *
     * - Subtraction cannot overflow.
     */
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        return a - b;
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `*` operator.
     *
     * Requirements:
     *
     * - Multiplication cannot overflow.
     */
    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        return a * b;
    }

    /**
     * @dev Returns the integer division of two unsigned integers, reverting on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator.
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        return a / b;
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * reverting when dividing by zero.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
        return a % b;
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
     * overflow (when the result is negative).
     *
     * CAUTION: This function is deprecated because it requires allocating memory for the error
     * message unnecessarily. For custom revert reasons use {trySub}.
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     *
     * - Subtraction cannot overflow.
     */
    function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        unchecked {
            require(b <= a, errorMessage);
            return a - b;
        }
    }

    /**
     * @dev Returns the integer division of two unsigned integers, reverting with custom message on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator. Note: this function uses a
     * `revert` opcode (which leaves remaining gas untouched) while Solidity
     * uses an invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        unchecked {
            require(b > 0, errorMessage);
            return a / b;
        }
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * reverting with custom message when dividing by zero.
     *
     * CAUTION: This function is deprecated because it requires allocating memory for the error
     * message unnecessarily. For custom revert reasons use {tryMod}.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        unchecked {
            require(b > 0, errorMessage);
            return a % b;
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/math/SignedMath.sol)

pragma solidity ^0.8.0;

/**
 * @dev Standard signed math utilities missing in the Solidity language.
 */
library SignedMath {
    /**
     * @dev Returns the largest of two signed numbers.
     */
    function max(int256 a, int256 b) internal pure returns (int256) {
        return a > b ? a : b;
    }

    /**
     * @dev Returns the smallest of two signed numbers.
     */
    function min(int256 a, int256 b) internal pure returns (int256) {
        return a < b ? a : b;
    }

    /**
     * @dev Returns the average of two signed numbers without overflow.
     * The result is rounded towards zero.
     */
    function average(int256 a, int256 b) internal pure returns (int256) {
        // Formula from the book "Hacker's Delight"
        int256 x = (a & b) + ((a ^ b) >> 1);
        return x + (int256(uint256(x) >> 255) & (a ^ b));
    }

    /**
     * @dev Returns the absolute unsigned value of a signed value.
     */
    function abs(int256 n) internal pure returns (uint256) {
        unchecked {
            // must be unchecked in order to support `n = type(int256).min`
            return uint256(n >= 0 ? n : -n);
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/Strings.sol)

pragma solidity ^0.8.0;

import "./math/Math.sol";
import "./math/SignedMath.sol";

/**
 * @dev String operations.
 */
library Strings {
    bytes16 private constant _SYMBOLS = "0123456789abcdef";
    uint8 private constant _ADDRESS_LENGTH = 20;

    /**
     * @dev Converts a `uint256` to its ASCII `string` decimal representation.
     */
    function toString(uint256 value) internal pure returns (string memory) {
        unchecked {
            uint256 length = Math.log10(value) + 1;
            string memory buffer = new string(length);
            uint256 ptr;
            /// @solidity memory-safe-assembly
            assembly {
                ptr := add(buffer, add(32, length))
            }
            while (true) {
                ptr--;
                /// @solidity memory-safe-assembly
                assembly {
                    mstore8(ptr, byte(mod(value, 10), _SYMBOLS))
                }
                value /= 10;
                if (value == 0) break;
            }
            return buffer;
        }
    }

    /**
     * @dev Converts a `int256` to its ASCII `string` decimal representation.
     */
    function toString(int256 value) internal pure returns (string memory) {
        return string(abi.encodePacked(value < 0 ? "-" : "", toString(SignedMath.abs(value))));
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
     */
    function toHexString(uint256 value) internal pure returns (string memory) {
        unchecked {
            return toHexString(value, Math.log256(value) + 1);
        }
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
     */
    function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
        bytes memory buffer = new bytes(2 * length + 2);
        buffer[0] = "0";
        buffer[1] = "x";
        for (uint256 i = 2 * length + 1; i > 1; --i) {
            buffer[i] = _SYMBOLS[value & 0xf];
            value >>= 4;
        }
        require(value == 0, "Strings: hex length insufficient");
        return string(buffer);
    }

    /**
     * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
     */
    function toHexString(address addr) internal pure returns (string memory) {
        return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
    }

    /**
     * @dev Returns true if the two strings are equal.
     */
    function equal(string memory a, string memory b) internal pure returns (bool) {
        return keccak256(bytes(a)) == keccak256(bytes(b));
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/structs/EnumerableSet.sol)
// This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.

pragma solidity ^0.8.0;

/**
 * @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 of the value in the `values` array, plus 1 because index 0
        // means a value is not in the set.
        mapping(bytes32 => uint256) _indexes;
    }

    /**
     * @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._indexes[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 read and store the value's index to prevent multiple reads from the same storage slot
        uint256 valueIndex = set._indexes[value];

        if (valueIndex != 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 toDeleteIndex = valueIndex - 1;
            uint256 lastIndex = set._values.length - 1;

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

                // Move the last value to the index where the value to delete is
                set._values[toDeleteIndex] = lastValue;
                // Update the index for the moved value
                set._indexes[lastValue] = valueIndex; // Replace lastValue's index to valueIndex
            }

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

            // Delete the index for the deleted slot
            delete set._indexes[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._indexes[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;

        /// @solidity memory-safe-assembly
        assembly {
            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;

        /// @solidity memory-safe-assembly
        assembly {
            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;

        /// @solidity memory-safe-assembly
        assembly {
            result := store
        }

        return result;
    }
}

//
//            __               ___ ___    __            _  
//           (_  | | |\/| |\/|  |   |    (_ \    / /\  |_) 
//           __) |_| |  | |  | _|_  |    __) \/\/ /--\ |   
//
//

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;

import "../interface/ISAVAX.sol";
import "../lib/SafeERC20.sol";
import "../SummitAdapter.sol";

interface IwAVAX {
    function withdraw(uint256) external;
}

/**
 * @notice wAVAX -> sAVAX
 **/
contract SAvaxAdapter is SummitAdapter {
    using SafeERC20 for IERC20;

    address public constant SAVAX = 0x2b2C81e08f1Af8835a78Bb2A90AE924ACE0eA4bE;
    address public constant WAVAX = 0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7;

    constructor(uint256 _swapGasEstimate) SummitAdapter("SAvaxAdapter", _swapGasEstimate) {
        _setAllowances();
    }

    function _query(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut
    ) internal view override returns (uint256 amountOut) {
        if (_tokenIn == WAVAX && _tokenOut == SAVAX && !ISAVAX(SAVAX).mintingPaused() && !_exceedsCap(_amountIn)) {
            amountOut = ISAVAX(SAVAX).getSharesByPooledAvax(_amountIn);
        }
    }

    function _swap(
        uint256 _amountIn,
        uint256 _amountOut,
        address,
        address _tokenOut,
        address _to
    ) internal override {
        IwAVAX(WAVAX).withdraw(_amountIn);
        uint256 shares = ISAVAX(SAVAX).submit{ value: _amountIn }();
        require(shares >= _amountOut, "SummitAdapter: Amount-out too low");
        _returnTo(_tokenOut, shares, _to);
    }

    function _exceedsCap(uint256 _amountIn) internal view returns (bool) {
        uint256 newBal = ISAVAX(SAVAX).totalPooledAvax() + _amountIn; // Assume U256::max won't be reached
        return newBal > ISAVAX(SAVAX).totalPooledAvaxCap();
    }

    function _setAllowances() internal {
        IERC20(WAVAX).safeApprove(WAVAX, UINT_MAX);
    }
}

//
//            __               ___ ___    __            _  
//           (_  | | |\/| |\/|  |   |    (_ \    / /\  |_) 
//           __) |_| |  | |  | _|_  |    __) \/\/ /--\ |   
//

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;

import "./UniswapV3AdapterBase.sol";

contract AgniAdapter is UniswapV3AdapterBase {

    constructor(
        string memory _name,
        uint256 _swapGasEstimate,
        uint256 _quoterGasLimit,
        address _quoter,
        address _factory,
        uint24[] memory _defaultFees
    ) UniswapV3AdapterBase(_name, _swapGasEstimate, _quoterGasLimit, _quoter, _factory, _defaultFees) {
    }

    function agniSwapCallback (
        int256 amount0Delta,
        int256 amount1Delta,
        bytes calldata
    ) external {
        if (amount0Delta > 0) {
            IERC20(IUniV3Pool(msg.sender).token0()).transfer(msg.sender, uint256(amount0Delta));
        } else {
            IERC20(IUniV3Pool(msg.sender).token1()).transfer(msg.sender, uint256(amount1Delta));
        }
    }
}

//
//            __               ___ ___    __            _  
//           (_  | | |\/| |\/|  |   |    (_ \    / /\  |_) 
//           __) |_| |  | |  | _|_  |    __) \/\/ /--\ |   
//

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;

import "./UniswapV3likeAdapter.sol";

interface IAlgebraFactory {
    function poolByPair(address, address) external view returns (address);
}

contract AlgebraAdapter is UniswapV3likeAdapter {
    using SafeERC20 for IERC20;

    address immutable FACTORY;

    constructor(
        string memory _name,
        uint256 _swapGasEstimate,
        uint256 _quoterGasLimit,
        address _quoter,
        address _factory
    ) UniswapV3likeAdapter(_name, _swapGasEstimate, _quoter, _quoterGasLimit) {
        FACTORY = _factory;
    }

    function getBestPool(
        address token0, 
        address token1
    ) internal view override returns (address mostLiquid) {
        return IAlgebraFactory(FACTORY).poolByPair(token0, token1);
    }

    function algebraSwapCallback(
        int256 amount0Delta,
        int256 amount1Delta,
        bytes calldata
    ) external {
        if (amount0Delta > 0) {
            IERC20(IUniV3Pool(msg.sender).token0()).transfer(msg.sender, uint256(amount0Delta));
        } else {
            IERC20(IUniV3Pool(msg.sender).token1()).transfer(msg.sender, uint256(amount1Delta));
        }
    }
}

//
//            __               ___ ___    __            _  
//           (_  | | |\/| |\/|  |   |    (_ \    / /\  |_) 
//           __) |_| |  | |  | _|_  |    __) \/\/ /--\ |   
//

// SPDX-License-Identifier: GPL-3.0-only

pragma solidity ^0.8.0;

import "../interface/IStabilityFund.sol";
import "../interface/IERC20.sol";
import "../lib/SafeERC20.sol";
import "../SummitAdapter.sol";

contract ArableSFAdapter is SummitAdapter {
    using SafeERC20 for IERC20;

    address public vault;
    mapping(address => uint256) public tokenDecimals;

    constructor(
        string memory _name,
        address _vault,
        uint256 _swapGasEstimate
    ) SummitAdapter(_name, _swapGasEstimate) {
        vault = _vault;
        setPoolTokens();
    }

    function setPoolTokens() public {
        uint256 whitelistedTknsLen = IStabilityFund(vault).getStableTokensCount();
        for (uint256 i = 0; i < whitelistedTknsLen; i++) {
            address token = IStabilityFund(vault).getStableTokens()[i];
            tokenDecimals[token] = IERC20(token).decimals();
            uint256 allowance = IERC20(token).allowance(address(this), vault);
            if (allowance < UINT_MAX) {
                IERC20(token).safeApprove(vault, UINT_MAX);
            }
        }
    }

    function adjustForDecimals(
        uint256 _amount,
        address _tokenDiv,
        address _tokenMul
    ) internal view returns (uint256) {
        uint256 decimalsDiv = tokenDecimals[_tokenDiv];
        uint256 decimalsMul = tokenDecimals[_tokenMul];
        return (_amount * 10**decimalsMul) / 10**decimalsDiv;
    }

    function hasVaultEnoughBal(address _token, uint256 _amount) private view returns (bool) {
        return IERC20(_token).balanceOf(vault) >= _amount;
    }

    function _query(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut
    ) internal view override returns (uint256) {
        if (
            _amountIn == 0 ||
            _tokenIn == _tokenOut ||
            !IStabilityFund(vault).isStableToken(_tokenIn) ||
            !IStabilityFund(vault).isStableToken(_tokenOut) ||
            IStabilityFund(vault).isTokenDisabled(_tokenIn) ||
            IStabilityFund(vault).isTokenDisabled(_tokenOut) ||
            !IStabilityFund(vault).swapEnabled()
        ) {
            return 0;
        }

        uint256 amountOut = adjustForDecimals(_amountIn, _tokenIn, _tokenOut);
        uint256 swapFee = IStabilityFund(vault).swapFee();
        uint256 swapFeeDivisor = 1 ether;
        uint256 feeAmount = (amountOut * swapFee) / swapFeeDivisor;
        uint256 amountOutAfterFees = amountOut - feeAmount;
        if (!hasVaultEnoughBal(_tokenOut, amountOutAfterFees)) {
            return 0;
        }

        return amountOutAfterFees;
    }

    function _swap(
        uint256 _amountIn,
        uint256 _amountOut,
        address _tokenIn,
        address _tokenOut,
        address _to
    ) internal override {
        IStabilityFund(vault).swap(_tokenIn, _amountIn, _tokenOut);
        // Confidently transfer amount-out
        _returnTo(_tokenOut, _amountOut, _to);
    }
}

//
//            __               ___ ___    __            _  
//           (_  | | |\/| |\/|  |   |    (_ \    / /\  |_) 
//           __) |_| |  | |  | _|_  |    __) \/\/ /--\ |   
//

// Supports Balancerlike pools

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;
import "../SummitAdapter.sol";
import "../interface/IVault.sol";
import "../interface/IBasePool.sol";
import "../interface/IMinimalSwapInfoPool.sol";

contract BalancerV2Adapter is SummitAdapter {
    using SafeERC20 for IERC20;

    address public vault;

    mapping(address => mapping(address => uint128)) internal poolToTokenIndex;
    mapping(address => mapping(address => address[])) internal tokensToPools;

    constructor(
        string memory _name,
        address _vault,
        address[] memory _pools,
        uint256 _swapGasEstimate
    ) SummitAdapter(_name, _swapGasEstimate) {
        vault = _vault;
        addPools(_pools);
    }

    function addPools(address[] memory _pools) public onlyMaintainer {
        for (uint128 i = 0; i < _pools.length; i++) {
            address pool = _pools[i];
            bytes32 poolId = IBasePool(pool).getPoolId();
            (IERC20[] memory tokens, , ) = IVault(vault).getPoolTokens(poolId);
            for (uint128 j = 0; j < tokens.length; j++) {
                address token = address(tokens[j]);
                poolToTokenIndex[pool][token] = j;
                for (uint128 k = 0; k < tokens.length; k++) {
                    if (j != k) {
                        tokensToPools[token][address(tokens[k])].push(pool);
                        _approveIfNeeded(token, UINT_MAX);
                    }
                }
            }
        }
    }

    function removePools(address[] memory _pools) public onlyMaintainer {
        for (uint256 i = 0; i < _pools.length; i++) {
            address pool = _pools[i];
            bytes32 poolId = IBasePool(pool).getPoolId();
            (IERC20[] memory tokens, , ) = IVault(vault).getPoolTokens(poolId);
            for (uint128 j = 0; j < tokens.length; j++) {
                address token = address(tokens[j]);
                for (uint128 k = 0; k < tokens.length; k++) {
                    if (j != k) {
                        address[] memory currentPools = tokensToPools[token][address(tokens[k])];
                        for (uint128 l = 0; l < currentPools.length; l++) {
                            if (currentPools[l] == pool) {
                                delete currentPools[l];
                            }
                        }
                        tokensToPools[token][address(tokens[k])] = currentPools;
                    }
                }
            }
        }
    }

    function getPools(address tokenIn, address tokenOut) public view returns (address[] memory) {
        return tokensToPools[tokenIn][tokenOut];
    }

    function _approveIfNeeded(address _tokenIn, uint256 _amount) internal {
        uint256 allowance = IERC20(_tokenIn).allowance(address(this), vault);
        if (allowance < _amount) {
            IERC20(_tokenIn).safeApprove(vault, _amount);
        }
    }

    function _query(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut
    ) internal view override returns (uint256) {
        if (_amountIn == 0 || _tokenIn == _tokenOut) {
            return 0;
        }

        address[] memory pools = getPools(_tokenIn, _tokenOut);
        if (pools.length == 0) {
            return 0;
        }

        (, uint256 amountOut) = _getBestPoolForSwap(pools, _tokenIn, _tokenOut, _amountIn);
        return amountOut;
    }

    function _swap(
        uint256 _amountIn,
        uint256 _amountOut,
        address _tokenIn,
        address _tokenOut,
        address to
    ) internal override {
        address[] memory pools = getPools(_tokenIn, _tokenOut);

        require(pools.length > 0, "No pools for swapping");

        (address pool, ) = _getBestPoolForSwap(pools, _tokenIn, _tokenOut, _amountIn);

        require(pool != address(0), "Undefined pool");

        IVault.SingleSwap memory swap;
        swap.poolId = IBasePool(pool).getPoolId();
        swap.kind = IVault.SwapKind.GIVEN_IN;
        swap.assetIn = _tokenIn;
        swap.assetOut = _tokenOut;
        swap.amount = _amountIn;
        swap.userData = "0x";

        IVault.FundManagement memory fund;
        fund.sender = address(this);
        fund.recipient = payable(to);
        fund.fromInternalBalance = false;
        fund.toInternalBalance = false;

        IVault(vault).swap(swap, fund, _amountOut, block.timestamp);
    }

    function _getBestPoolForSwap(
        address[] memory pools,
        address _tokenIn,
        address _tokenOut,
        uint256 _amountIn
    ) internal view returns (address bestPool, uint256 amountOut) {
        amountOut = 0;
        bestPool = address(0);
        for (uint128 i; i < pools.length; i++) {
            address pool = pools[i];
            if (pool == address(0)) {
                continue;
            }
            IPoolSwapStructs.SwapRequest memory request;
            request.poolId = IBasePool(pool).getPoolId();
            request.kind = IVault.SwapKind.GIVEN_IN;
            request.tokenIn = IERC20(_tokenIn);
            request.tokenOut = IERC20(_tokenOut);
            request.amount = _amountIn;
            request.userData = "0x";
            uint256 newAmountOut = _getAmountOut(request, pool);
            if (newAmountOut > amountOut) {
                amountOut = newAmountOut;
                bestPool = pool;
            }
        }
    }

    function _getAmountOut(IPoolSwapStructs.SwapRequest memory request, address pool)
        internal
        view
        returns (uint256 amountOut)
    {
        // Based on https://github.com/balancer-labs/balancer-v2-monorepo/blob/master/pkg/vault/contracts/Swaps.sol#L275
        (, uint256[] memory balances, ) = IVault(vault).getPoolTokens(request.poolId);
        uint256 tokenInTotal = balances[poolToTokenIndex[pool][address(request.tokenIn)]];
        uint256 tokenOutTotal = balances[poolToTokenIndex[pool][address(request.tokenOut)]];
        amountOut = _getAmountOutSafe(request, tokenInTotal, tokenOutTotal, pool);
    }

    function _getAmountOutSafe(
        IPoolSwapStructs.SwapRequest memory request,
        uint256 tokenInTotal,
        uint256 tokenOutTotal,
        address pool
    ) internal view returns (uint256) {
        try IMinimalSwapInfoPool(pool).onSwap(request, tokenInTotal, tokenOutTotal) returns (uint256 amountOut) {
            return amountOut;
        } catch {}
    }
}

//
//            __               ___ ___    __            _  
//           (_  | | |\/| |\/|  |   |    (_ \    / /\  |_) 
//           __) |_| |  | |  | _|_  |    __) \/\/ /--\ |   
//

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;

import "../interface/IERC20.sol";
import "../lib/SafeERC20.sol";
import "../SummitAdapter.sol";

contract BridgeMigrationAdapter is SummitAdapter {
    using SafeERC20 for IERC20;

    mapping(address => bool) public isNewBridgeToken;

    constructor(
        address[] memory _newTokens,
        address[] memory _oldTokens,
        uint256 _swapGasEstimate
    ) SummitAdapter("BridgeMigrationAdapter", _swapGasEstimate) {
        setNewBridgeTokens(_newTokens, _oldTokens);
    }

    function _query(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut
    ) internal view override returns (uint256 amountOut) {
        if (isNewBridgeToken[_tokenOut] && IERC20(_tokenOut).swapSupply(_tokenIn) >= _amountIn) amountOut = _amountIn;
    }

    function _swap(
        uint256 _amountIn,
        uint256 _amountOut,
        address _tokenIn,
        address _tokenOut,
        address _to
    ) internal override {
        IERC20(_tokenOut).swap(_tokenIn, _amountIn);
        _returnTo(_tokenOut, _amountOut, _to);
    }

    function setNewBridgeTokens(address[] memory _newTokens, address[] memory _oldTokens) public onlyMaintainer {
        require(_newTokens.length == _oldTokens.length, "Needs to be surjective");
        for (uint256 i; i < _newTokens.length; i++) {
            require(IERC20(_newTokens[i]).swapSupply(_oldTokens[i]) > 0, "Invalid combination");
            _approveIfNeeded(_newTokens[i], _oldTokens[i]);
            isNewBridgeToken[_newTokens[i]] = true;
        }
    }

    function _approveIfNeeded(address _newToken, address _oldToken) internal {
        uint256 allowance = IERC20(_oldToken).allowance(address(this), _newToken);
        if (allowance < UINT_MAX) IERC20(_oldToken).safeApprove(_newToken, UINT_MAX);
    }
}

//
//            __               ___ ___    __            _  
//           (_  | | |\/| |\/|  |   |    (_ \    / /\  |_) 
//           __) |_| |  | |  | _|_  |    __) \/\/ /--\ |   
//

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;

import "../interface/IERC20.sol";
import "../lib/SafeERC20.sol";
import "../SummitAdapter.sol";

interface IFactory {
    function getPair(address,address) external view returns (address);
}

interface IPair {
    function getAmountOut(uint256, address) external view returns (uint256);
    function swap(
        uint256 amount0Out,
        uint256 amount1Out,
        address to,
        bytes calldata data,
        address referrer
    ) external;
}

contract CamelotAdapter is SummitAdapter {
    using SafeERC20 for IERC20;

    address immutable FACTORY;
    address referrer;

    constructor(
        string memory _name,
        address _factory,
        uint256 _swapGasEstimate
    ) SummitAdapter(_name, _swapGasEstimate) {
        FACTORY = _factory;
    }

    function setReferrer(address _referrer) public onlyMaintainer {
        referrer = _referrer;
    } 

    function getQuoteAndPair(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut
    ) internal view returns (uint256 amountOut, address pair) {
        pair = IFactory(FACTORY).getPair(_tokenIn, _tokenOut);
        if (pair != address(0))
            amountOut = IPair(pair).getAmountOut(_amountIn, _tokenIn);
    }

    function _query(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut
    ) internal view override returns (uint256 amountOut) {
        if (_tokenIn != _tokenOut && _amountIn != 0)
            (amountOut, ) = getQuoteAndPair(_amountIn, _tokenIn, _tokenOut);
    }

    function _swap(
        uint256 _amountIn,
        uint256 _amountOut,
        address _tokenIn,
        address _tokenOut,
        address to
    ) internal override {
        (uint256 amountOut, address pair) = getQuoteAndPair(_amountIn, _tokenIn, _tokenOut);
        require(amountOut >= _amountOut, "Insufficent amount out");
        (uint256 amount0Out, uint256 amount1Out) = (_tokenIn < _tokenOut)
            ? (uint256(0), amountOut)
            : (amountOut, uint256(0));
        IERC20(_tokenIn).safeTransfer(pair, _amountIn);
        IPair(pair).swap(amount0Out, amount1Out, to, new bytes(0), referrer);
    }
}

//
//            __               ___ ___    __            _  
//           (_  | | |\/| |\/|  |   |    (_ \    / /\  |_) 
//           __) |_| |  | |  | _|_  |    __) \/\/ /--\ |   
//

// Supports Curve Atricrypto pools and alike

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;

import "../interface/ICurve1.sol";
import "../interface/IERC20.sol";
import "../lib/SafeERC20.sol";
import "../SummitAdapter.sol";

contract Curve1Adapter is SummitAdapter {
    using SafeERC20 for IERC20;

    mapping(address => uint256) public tokenIndex;
    mapping(address => bool) public isPoolToken;
    address public pool;

    constructor(
        string memory _name,
        address _pool,
        uint256 _swapGasEstimate
    ) SummitAdapter(_name, _swapGasEstimate) {
        pool = _pool;
        _setPoolTokens();
    }

    // Mapping indicator which tokens are included in the pool
    function _setPoolTokens() internal {
        for (uint256 i = 0; true; ++i) {
            try ICurve1(pool).underlying_coins(i) returns (address token) {
                _setPoolTokenAllowance(token);
                isPoolToken[token] = true;
                tokenIndex[token] = i;
            } catch {
                break;
            }
        }
    }

    function _setPoolTokenAllowance(address _token) internal {
        IERC20(_token).approve(pool, UINT_MAX);
    }

    function _query(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut
    ) internal view override returns (uint256) {
        if (_amountIn == 0 || _tokenIn == _tokenOut || !isPoolToken[_tokenIn] || !isPoolToken[_tokenOut]) {
            return 0;
        }
        try ICurve1(pool).get_dy_underlying(tokenIndex[_tokenIn], tokenIndex[_tokenOut], _amountIn) returns (
            uint256 amountOut
        ) {
            return _applyError(amountOut);
        } catch {
            return 0;
        }
    }

    function _applyError(uint256 _amount) internal pure returns (uint256) {
        // `calc_token_amount` in base_pool is used in part of the query
        // this method does account for deposit fee which causes discrepancy
        // between the query result and the actual swap amount by few bps(0-3.2)
        // Additionally there is a rounding error (swap and query may calc different amounts)
        // Account for above with 4 bps discount
        return _amount == 0 ? 0 : (_amount * (1e4 - 4)) / 1e4;
    }

    function _swap(
        uint256 _amountIn,
        uint256 _amountOut,
        address _tokenIn,
        address _tokenOut,
        address _to
    ) internal override {
        ICurve1(pool).exchange_underlying(tokenIndex[_tokenIn], tokenIndex[_tokenOut], _amountIn, _amountOut);
        uint256 balThis = IERC20(_tokenOut).balanceOf(address(this));
        _returnTo(_tokenOut, balThis, _to);
    }
}

//
//            __               ___ ___    __            _  
//           (_  | | |\/| |\/|  |   |    (_ \    / /\  |_) 
//           __) |_| |  | |  | _|_  |    __) \/\/ /--\ |   
//

// Supports Curve AAVE and Ren pool and alike

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;

import "../interface/ICurve2.sol";
import "../interface/IERC20.sol";
import "../lib/SafeERC20.sol";
import "../SummitAdapter.sol";

contract Curve2Adapter is SummitAdapter {
    using SafeERC20 for IERC20;

    uint256 constant BPS_DEN = 1e4;
    uint256 constant MAX_ERR_BPS = 1;

    mapping(address => bool) public isPoolToken;
    mapping(address => int128) public tokenIndex;
    address public pool;

    constructor(
        string memory _name,
        address _pool,
        uint256 _swapGasEstimate
    ) SummitAdapter(_name, _swapGasEstimate) {
        pool = _pool;
        _setPoolTokens();
    }

    // Mapping indicator which tokens are included in the pool
    function _setPoolTokens() internal {
        for (uint256 i = 0; true; i++) {
            try ICurve2(pool).underlying_coins(i) returns (address token) {
                _setPoolTokenAllowance(token);
                isPoolToken[token] = true;
                tokenIndex[token] = int128(int256(i));
            } catch {
                break;
            }
        }
    }

    function _setPoolTokenAllowance(address _token) internal {
        IERC20(_token).approve(pool, UINT_MAX);
    }

    function _query(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut
    ) internal view override returns (uint256) {
        if (_amountIn == 0 || _tokenIn == _tokenOut || !isPoolToken[_tokenIn] || !isPoolToken[_tokenOut]) {
            return 0;
        }
        try ICurve2(pool).get_dy_underlying(tokenIndex[_tokenIn], tokenIndex[_tokenOut], _amountIn) returns (
            uint256 amountOut
        ) {
            return _applyErr(amountOut);
        } catch {
            return 0;
        }
    }

    function _applyErr(uint256 x) internal pure returns (uint256) {
        return (x * (BPS_DEN - MAX_ERR_BPS)) / BPS_DEN;
    }

    function _swap(
        uint256 _amountIn,
        uint256 _amountOut,
        address _tokenIn,
        address _tokenOut,
        address _to
    ) internal override {
        ICurve2(pool).exchange_underlying(tokenIndex[_tokenIn], tokenIndex[_tokenOut], _amountIn, 0);
        uint256 balThis = IERC20(_tokenOut).balanceOf(address(this));
        require(balThis >= _amountOut, "Insufficient amount out");
        _returnTo(_tokenOut, balThis, _to);
    }
}

//
//            __               ___ ___    __            _  
//           (_  | | |\/| |\/|  |   |    (_ \    / /\  |_) 
//           __) |_| |  | |  | _|_  |    __) \/\/ /--\ |   
//

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;

import "../interface/ICurveMeta.sol";
import "../interface/IERC20.sol";
import "../lib/SafeERC20.sol";
import "../SummitAdapter.sol";

contract CurveMetaAdapter is SummitAdapter {
    using SafeERC20 for IERC20;

    address public immutable POOL;
    address public immutable COIN;
    mapping(address => int128) public tokenIndex;
    mapping(address => bool) public isPoolToken;

    constructor(
        string memory _name,
        address _pool,
        uint256 _swapGasEstimate
    ) SummitAdapter(_name, _swapGasEstimate) {
        POOL = _pool;
        COIN = _setPoolTokens(_pool);
    }

    function _setPoolTokens(address _pool) internal returns (address coin0) {
        coin0 = ICurveMeta(_pool).coins(0);
        _approveToken(_pool, coin0, 0);
        for (uint256 i = 0; true; i++) {
            try ICurveMeta(_pool).base_coins(i) returns (address token) {
                _approveToken(_pool, token, int128(int256(i)) + 1);
            } catch {
                break;
            }
        }
    }

    function _approveToken(
        address _pool,
        address _token,
        int128 _index
    ) internal {
        IERC20(_token).safeApprove(_pool, UINT_MAX);
        tokenIndex[_token] = _index;
        isPoolToken[_token] = true;
    }

    function _query(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut
    ) internal view override returns (uint256) {
        if (
            _amountIn == 0 ||
            _tokenIn == _tokenOut ||
            !((_tokenIn == COIN && isPoolToken[_tokenOut]) || (_tokenOut == COIN && isPoolToken[_tokenIn]))
        ) {
            return 0;
        }
        try ICurveMeta(POOL).get_dy_underlying(tokenIndex[_tokenIn], tokenIndex[_tokenOut], _amountIn) returns (
            uint256 amountOut
        ) {
            // `calc_token_amount` in base_pool is used in part of the query
            // this method does account for deposit fee which causes discrepancy
            // between the query result and the actual swap amount by few bps(0-3.2)
            // Additionally there is a rounding error (swap and query may calc different amounts)
            // Account for that with 1 bps discount
            return amountOut == 0 ? 0 : (amountOut * (1e4 - 1)) / 1e4;
        } catch {
            return 0;
        }
    }

    function _swap(
        uint256 _amountIn,
        uint256 _amountOut,
        address _tokenIn,
        address _tokenOut,
        address _to
    ) internal override {
        ICurveMeta(POOL).exchange_underlying(tokenIndex[_tokenIn], tokenIndex[_tokenOut], _amountIn, _amountOut);
        _returnTo(_tokenOut, IERC20(_tokenOut).balanceOf(address(this)), _to);
    }
}

//
//            __               ___ ___    __            _  
//           (_  | | |\/| |\/|  |   |    (_ \    / /\  |_) 
//           __) |_| |  | |  | _|_  |    __) \/\/ /--\ |   
//

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;

import "../interface/IERC20.sol";
import "../lib/SafeERC20.sol";
import "../SummitAdapter.sol";

interface IMetaPool {
    function get_dy_underlying(
        int128,
        int128,
        uint256
    ) external view returns (uint256);

    function exchange_underlying(
        int128,
        int128,
        uint256,
        uint256
    ) external;

    function coins(uint256) external view returns (address);
}

interface IBasePool {
    function coins(uint256) external view returns (address);
}

contract CurveMetaV2Adapter is SummitAdapter {
    using SafeERC20 for IERC20;

    address public immutable META_COIN;
    address public immutable POOL;
    mapping(address => int128) public tokenIndex;
    mapping(address => bool) public isPoolToken;

    constructor(
        string memory _name,
        address _pool,
        uint256 _swapGasEstimate
    ) SummitAdapter(_name, _swapGasEstimate) {
        address metaCoin = getMetaCoin(_pool);
        approveAndAddTokenToAdapter(_pool, metaCoin, 0);
        addUnderlyingTkns(_pool);
        META_COIN = metaCoin;
        POOL = _pool;
    }

    function getMetaCoin(address _pool) internal view returns (address) {
        return IMetaPool(_pool).coins(0);
    }

    function initPoolAndReturnMetaTkn(address _pool) internal returns (address coin0) {
        coin0 = IMetaPool(_pool).coins(0);
        approveAndAddTokenToAdapter(_pool, coin0, 0);
    }

    function addUnderlyingTkns(address metaPool) internal {
        address basePool = IMetaPool(metaPool).coins(1);
        for (uint256 i; true; ++i) {
            address token = getUnderlyingToken(basePool, i);
            if (token == address(0)) break;
            approveAndAddTokenToAdapter(metaPool, token, int128(int256(i)) + 1);
        }
    }

    function getUnderlyingToken(address basePool, uint256 i) internal view returns (address) {
        try IBasePool(basePool).coins(i) returns (address token) {
            return token;
        } catch {}
    }

    function approveAndAddTokenToAdapter(
        address _pool,
        address _token,
        int128 _index
    ) internal {
        IERC20(_token).safeApprove(_pool, UINT_MAX);
        tokenIndex[_token] = _index;
        isPoolToken[_token] = true;
    }

    function _query(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut
    ) internal view override returns (uint256) {
        if (!validInputParams(_amountIn, _tokenIn, _tokenOut)) return 0;
        // `calc_token_amount` in base_pool is used in part of the query
        // this method does account for deposit fee which causes discrepancy
        // between the query result and the actual swap amount by few bps(0-3.2)
        // Additionally there is a rounding error (swap and query may calc different amounts)
        // Account for that with 1 bps discount
        uint256 amountOut = safeQuery(_amountIn, _tokenIn, _tokenOut);
        return (amountOut * (1e4 - 1)) / 1e4;
    }

    function safeQuery(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut
    ) internal view returns (uint256) {
        try IMetaPool(POOL).get_dy_underlying(tokenIndex[_tokenIn], tokenIndex[_tokenOut], _amountIn) returns (
            uint256 amountOut
        ) {
            return amountOut;
        } catch {
            return 0;
        }
    }

    function validInputParams(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut
    ) internal view returns (bool) {
        return _amountIn != 0 && _tokenIn != _tokenOut && validPath(_tokenIn, _tokenOut);
    }

    function validPath(address tkn0, address tkn1) internal view returns (bool) {
        return (tkn0 == META_COIN && isPoolToken[tkn1]) || (tkn1 == META_COIN && isPoolToken[tkn0]);
    }

    function _swap(
        uint256 _amountIn,
        uint256 _amountOut,
        address _tokenIn,
        address _tokenOut,
        address _to
    ) internal override {
        IMetaPool(POOL).exchange_underlying(tokenIndex[_tokenIn], tokenIndex[_tokenOut], _amountIn, _amountOut);
        uint256 balThis = IERC20(_tokenOut).balanceOf(address(this));
        _returnTo(_tokenOut, balThis, _to);
    }
}

//
//            __               ___ ___    __            _  
//           (_  | | |\/| |\/|  |   |    (_ \    / /\  |_) 
//           __) |_| |  | |  | _|_  |    __) \/\/ /--\ |   
//

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;

import "../interface/IERC20.sol";
import "../lib/SafeERC20.sol";
import "../SummitAdapter.sol";

interface IMetaPool {
    function get_dy_underlying(
        int128,
        int128,
        uint256
    ) external view returns (uint256);

    function exchange_underlying(
        int128,
        int128,
        uint256,
        uint256
    ) external;

    function coins(uint256) external view returns (address);
}

interface IBasePool {
    function coins(uint256) external view returns (address);
}

contract CurveMetaV3Adapter is SummitAdapter {
    using SafeERC20 for IERC20;

    mapping(address => mapping(address => int128)) public tokenIndexForPool;
    mapping(address => mapping(address => address)) public poolForTokens;

    constructor(
        string memory _name,
        address[] memory _pools,
        uint256 _swapGasEstimate
    ) SummitAdapter(_name, _swapGasEstimate) {
        setPools(_pools);
    }

    function getPool(address tkn0, address tkn1) public view returns (address) {
        return poolForTokens[tkn0][tkn1];
    }

    function rmPools(address[] memory _pools) external onlyMaintainer {
        for (uint256 i; i < _pools.length; ++i) _rmPool(_pools[i]);
    }

    function setPools(address[] memory _pools) public onlyMaintainer {
        for (uint256 i; i < _pools.length; ++i) _setPool(_pools[i]);
    }

    function _setPool(address _pool) internal {
        (address mToken, address basePool) = getCoins(_pool);
        IERC20(mToken).safeApprove(_pool, UINT_MAX);
        for (uint256 i; true; ++i) {
            address uToken = getUnderlyingToken(basePool, i);
            if (uToken == address(0)) break;
            _setTokenPair(_pool, mToken, uToken, int128(int256(i)));
        }
    }

    function _rmPool(address _pool) internal {
        (address mToken, address basePool) = getCoins(_pool);
        for (uint256 i; true; ++i) {
            address uToken = getUnderlyingToken(basePool, i);
            if (uToken == address(0)) break;
            poolForTokens[uToken][mToken] = address(0);
            poolForTokens[mToken][uToken] = address(0);
        }
    }

    function getCoins(address _pool) internal view returns (address meta, address base) {
        meta = IMetaPool(_pool).coins(0);
        base = IMetaPool(_pool).coins(1);
    }

    function _setTokenPair(
        address _pool,
        address _metaTkn,
        address _uToken,
        int128 _index
    ) internal {
        IERC20(_uToken).safeApprove(_pool, UINT_MAX);
        tokenIndexForPool[_pool][_uToken] = _index + 1;
        poolForTokens[_uToken][_metaTkn] = _pool;
        poolForTokens[_metaTkn][_uToken] = _pool;
    }

    function getUnderlyingToken(address basePool, uint256 i) internal view returns (address) {
        try IBasePool(basePool).coins(i) returns (address token) {
            return token;
        } catch {}
    }

    function _query(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut
    ) internal view override returns (uint256) {
        address pool = getPool(_tokenIn, _tokenOut);
        if (pool == address(0) || _amountIn == 0) return 0;
        // `calc_token_amount` in base_pool is used in part of the query
        // this method does account for deposit fee which causes discrepancy
        // between the query result and the actual swap amount by few bps(0-3.2)
        // Additionally there is a rounding error (swap and query may calc different amounts)
        // Account for that with 1 bps discount
        uint256 amountOut = safeQuery(pool, _amountIn, _tokenIn, _tokenOut);
        return (amountOut * (1e4 - 1)) / 1e4;
    }

    function safeQuery(
        address _pool,
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut
    ) internal view returns (uint256) {
        try
            IMetaPool(_pool).get_dy_underlying(
                tokenIndexForPool[_pool][_tokenIn],
                tokenIndexForPool[_pool][_tokenOut],
                _amountIn
            )
        returns (uint256 amountOut) {
            return amountOut;
        } catch {
            return 0;
        }
    }

    function _swap(
        uint256 _amountIn,
        uint256 _amountOut,
        address _tokenIn,
        address _tokenOut,
        address _to
    ) internal override {
        address pool = getPool(_tokenIn, _tokenOut);
        IMetaPool(pool).exchange_underlying(
            tokenIndexForPool[pool][_tokenIn],
            tokenIndexForPool[pool][_tokenOut],
            _amountIn,
            _amountOut
        );
        uint256 balThis = IERC20(_tokenOut).balanceOf(address(this));
        _returnTo(_tokenOut, balThis, _to);
    }
}

//
//            __               ___ ___    __            _  
//           (_  | | |\/| |\/|  |   |    (_ \    / /\  |_) 
//           __) |_| |  | |  | _|_  |    __) \/\/ /--\ |   
//

// Supports Curve MIM pool (manually enter base tokens)

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;

import "../interface/ICurveMeta.sol";
import "../interface/ICurve2.sol";
import "../interface/IERC20.sol";
import "../lib/SafeERC20.sol";
import "../SummitAdapter.sol";

interface ICurveSwapper128 {
    function exchange_underlying(
        address pool,
        int128 i,
        int128 j,
        uint256 dx,
        uint256 minDy
    ) external;
}

contract CurveMetaWithSwapperAdapter is SummitAdapter {
    using SafeERC20 for IERC20;

    address public immutable metaPool;
    address public immutable basePool;
    address public immutable swapper;
    address public immutable metaTkn;
    mapping(address => int128) public tokenIndex;
    mapping(address => bool) public isPoolToken;

    constructor(
        string memory _name,
        uint256 _swapGasEstimate,
        address _metaPool,
        address _basePool,
        address _swapper
    ) SummitAdapter(_name, _swapGasEstimate) {
        metaTkn = setMetaTkn(_metaPool, _swapper);
        metaPool = _metaPool;
        basePool = _basePool;
        swapper = _swapper;
        _setUnderlyingTokens(_basePool, _swapper);
    }

    // Mapping indicator which tokens are included in the pool
    function _setUnderlyingTokens(address _basePool, address _swapper) internal {
        for (uint256 i = 0; true; i++) {
            try ICurve2(_basePool).underlying_coins(i) returns (address token) {
                _setPoolTokenAllowance(token, _swapper);
                isPoolToken[token] = true;
                tokenIndex[token] = int128(int256(i)) + 1;
            } catch {
                break;
            }
        }
    }

    function setMetaTkn(address _metaPool, address _swapper) internal returns (address _metaTkn) {
        _metaTkn = ICurveMeta(_metaPool).coins(0);
        _setPoolTokenAllowance(_metaTkn, _swapper);
        isPoolToken[_metaTkn] = true;
        tokenIndex[_metaTkn] = 0;
    }

    function _setPoolTokenAllowance(address _token, address _target) internal {
        IERC20(_token).approve(_target, UINT_MAX);
    }

    function _query(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut
    ) internal view override returns (uint256) {
        if (!validInputParams(_amountIn, _tokenIn, _tokenOut)) {
            return 0;
        }
        try ICurveMeta(metaPool).get_dy_underlying(tokenIndex[_tokenIn], tokenIndex[_tokenOut], _amountIn) returns (
            uint256 amountOut
        ) {
            // `calc_token_amount` in base_pool is used in part of the query
            // this method does account for deposit fee which causes discrepancy
            // between the query result and the actual swap amount by few bps(0-3.2)
            // Additionally there is a rounding error (swap and query may calc different amounts)
            // Account for that with 4 bps discount
            return amountOut == 0 ? 0 : (amountOut * (1e4 - 4)) / 1e4;
        } catch {
            return 0;
        }
    }

    function validInputParams(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut
    ) internal view returns (bool) {
        return _amountIn != 0 && _tokenIn != _tokenOut && validPath(_tokenIn, _tokenOut);
    }

    function validPath(address tkn0, address tkn1) internal view returns (bool) {
        return (tkn0 == metaTkn && isPoolToken[tkn1]) || (tkn1 == metaTkn && isPoolToken[tkn0]);
    }

    function _swap(
        uint256 _amountIn,
        uint256 _amountOut,
        address _tokenIn,
        address _tokenOut,
        address _to
    ) internal override {
        ICurveSwapper128(swapper).exchange_underlying(
            metaPool,
            tokenIndex[_tokenIn],
            tokenIndex[_tokenOut],
            _amountIn,
            0
        );
        uint256 balThis = IERC20(_tokenOut).balanceOf(address(this));
        require(balThis >= _amountOut, "Insufficient amount-out");
        _returnTo(_tokenOut, balThis, _to);
    }
}

//
//            __               ___ ___    __            _  
//           (_  | | |\/| |\/|  |   |    (_ \    / /\  |_) 
//           __) |_| |  | |  | _|_  |    __) \/\/ /--\ |   
//

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;

import "../interface/ICurvePlain128.sol";
import "../interface/IERC20.sol";
import "../lib/SafeERC20.sol";
import "../SummitAdapter.sol";

contract CurvePlain128Adapter is SummitAdapter {
    using SafeERC20 for IERC20;

    address public immutable POOL;
    mapping(address => int128) public tokenIndex;
    mapping(address => bool) public isPoolToken;

    constructor(
        string memory _name,
        address _pool,
        uint256 _swapGasEstimate
    ) SummitAdapter(_name, _swapGasEstimate) {
        POOL = _pool;
        _setPoolTokens(_pool);
    }

    // Mapping indicator which tokens are included in the pool
    function _setPoolTokens(address _pool) internal {
        for (uint256 i = 0; true; i++) {
            try ICurvePlain128(_pool).coins(i) returns (address token) {
                _approveToken(_pool, token, int128(int256(i)));
            } catch {
                break;
            }
        }
    }

    function _approveToken(
        address _pool,
        address _token,
        int128 _index
    ) internal {
        IERC20(_token).safeApprove(_pool, UINT_MAX);
        tokenIndex[_token] = _index;
        isPoolToken[_token] = true;
    }

    function _query(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut
    ) internal view override returns (uint256) {
        if (!_validArgs(_amountIn, _tokenIn, _tokenOut)) return 0;
        uint256 amountOut = _getDySafe(_amountIn, _tokenIn, _tokenOut);
        // Account for possible rounding error
        return amountOut > 0 ? amountOut - 1 : 0;
    }

    function _validArgs(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut
    ) internal view returns (bool) {
        return _amountIn != 0 && _tokenIn != _tokenOut && isPoolToken[_tokenIn] && isPoolToken[_tokenOut];
    }

    function _getDySafe(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut
    ) internal view returns (uint256) {
        try ICurvePlain128(POOL).get_dy(tokenIndex[_tokenIn], tokenIndex[_tokenOut], _amountIn) returns (
            uint256 amountOut
        ) {
            return amountOut;
        } catch {
            return 0;
        }
    }

    function _swap(
        uint256 _amountIn,
        uint256 _amountOut,
        address _tokenIn,
        address _tokenOut,
        address _to
    ) internal override {
        ICurvePlain128(POOL).exchange(tokenIndex[_tokenIn], tokenIndex[_tokenOut], _amountIn, _amountOut);
        // Confidently transfer amount-out
        _returnTo(_tokenOut, _amountOut, _to);
    }
}

//
//            __               ___ ___    __            _  
//           (_  | | |\/| |\/|  |   |    (_ \    / /\  |_) 
//           __) |_| |  | |  | _|_  |    __) \/\/ /--\ |   
//

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;

import "../interface/ICurvePlain128Native.sol";
import "../interface/IERC20.sol";
import "../interface/IWETH.sol";
import "../lib/SafeERC20.sol";
import "../SummitAdapter.sol";


contract CurvePlain128NativeAdapter is SummitAdapter {
    using SafeERC20 for IERC20;

    address constant NATIVE = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
    address immutable WNATIVE;
    address public immutable POOL;
    mapping(address => int128) public tokenIndex;
    mapping(address => bool) public isPoolToken;

    constructor(
        string memory _name,
        uint256 _swapGasEstimate,
        address _pool,
        address _wNative
    ) SummitAdapter(_name, _swapGasEstimate) {
        IERC20(_wNative).safeApprove(_wNative, UINT_MAX);
        _setPoolTokens(_pool, _wNative);
        WNATIVE = _wNative;
        POOL = _pool;
    }

    // Mapping indicator which tokens are included in the pool
    function _setPoolTokens(address _pool, address _wNative) internal {
        for (uint256 i = 0; true; i++) {
            try ICurvePlain128Native(_pool).coins(i) returns (address token) {
                _addTokenToPool(_pool, token, int128(int256(i)), _wNative);
            } catch {
                break;
            }
        }
    }

    function _addTokenToPool(
        address _pool,
        address _token,
        int128 _index, 
        address _wNative
    ) internal {
        if (_token != NATIVE) {
            IERC20(_token).safeApprove(_pool, UINT_MAX);
        } else {
            _token = _wNative;
        }
        tokenIndex[_token] = _index;
        isPoolToken[_token] = true;
    }

    function _query(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut
    ) internal view override returns (uint256) {
        if (!_validArgs(_amountIn, _tokenIn, _tokenOut)) return 0;
        uint256 amountOut = _getDySafe(_amountIn, _tokenIn, _tokenOut);
        // Account for possible rounding error
        return amountOut > 0 ? amountOut - 1 : 0;
    }

    function _validArgs(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut
    ) internal view returns (bool) {
        return _amountIn != 0 && 
            _tokenIn != _tokenOut && 
            isPoolToken[_tokenIn] && 
            isPoolToken[_tokenOut];
    }

    function _getDySafe(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut
    ) internal view returns (uint256) {
        try ICurvePlain128Native(POOL).get_dy(tokenIndex[_tokenIn], tokenIndex[_tokenOut], _amountIn) returns (
            uint256 amountOut
        ) {
            return amountOut;
        } catch {
            return 0;
        }
    }

    function _swap(
        uint256 _amountIn,
        uint256 _amountOut,
        address _tokenIn,
        address _tokenOut,
        address _to
    ) internal override {
        uint256 transferVal;
        if (_tokenIn == WNATIVE) {
            transferVal = _amountIn;
            IWETH(WNATIVE).withdraw(_amountIn);
        }
        uint256 dy = ICurvePlain128Native(POOL).exchange{ value: transferVal }(
            tokenIndex[_tokenIn], 
            tokenIndex[_tokenOut], 
            _amountIn, 
            _amountOut
        );
        if (_tokenOut == WNATIVE) {
            IWETH(WNATIVE).deposit{ value: dy }();
        }
        _returnTo(_tokenOut, dy, _to);
    }
}

//
//            __               ___ ___    __            _  
//           (_  | | |\/| |\/|  |   |    (_ \    / /\  |_) 
//           __) |_| |  | |  | _|_  |    __) \/\/ /--\ |   
//

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;

import "../interface/ICurvePlain256.sol";
import "../interface/IERC20.sol";
import "../lib/SafeERC20.sol";
import "../SummitAdapter.sol";

contract CurvePlain256Adapter is SummitAdapter {
    using SafeERC20 for IERC20;

    address public immutable POOL;
    mapping(address => uint256) public tokenIndex;
    mapping(address => bool) public isPoolToken;

    constructor(
        string memory _name,
        address _pool,
        uint256 _swapGasEstimate
    ) SummitAdapter(_name, _swapGasEstimate) {
        name = _name;
        POOL = _pool;
        _setPoolTokens(_pool);
        setSwapGasEstimate(_swapGasEstimate);
    }

    function _setPoolTokens(address _pool) internal {
        for (uint256 i = 0; true; i++) {
            address token = _getCoinByIndexSafe(_pool, i);
            if (token == address(0)) break;
            _addToken(_pool, token, i);
        }
    }

    function _getCoinByIndexSafe(address _pool, uint256 _index) internal view returns (address token) {
        try ICurvePlain256(_pool).coins(_index) returns (address _token) {
            token = _token;
        } catch {}
    }

    function _addToken(
        address _pool,
        address _token,
        uint256 _index
    ) internal {
        IERC20(_token).safeApprove(_pool, UINT_MAX);
        tokenIndex[_token] = _index;
        isPoolToken[_token] = true;
    }

    function _query(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut
    ) internal view override returns (uint256) {
        if (!_validArgs(_amountIn, _tokenIn, _tokenOut)) return 0;
        uint256 amountOut = _getDySafe(_amountIn, _tokenIn, _tokenOut);
        // Account for possible rounding error
        return amountOut > 0 ? amountOut - 1 : 0;
    }

    function _validArgs(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut
    ) internal view returns (bool) {
        return _amountIn != 0 && _tokenIn != _tokenOut && isPoolToken[_tokenIn] && isPoolToken[_tokenOut];
    }

    function _getDySafe(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut
    ) internal view returns (uint256) {
        try ICurvePlain256(POOL).get_dy(tokenIndex[_tokenIn], tokenIndex[_tokenOut], _amountIn) returns (
            uint256 amountOut
        ) {
            return amountOut;
        } catch {
            return 0;
        }
    }

    function _swap(
        uint256 _amountIn,
        uint256 _amountOut,
        address _tokenIn,
        address _tokenOut,
        address _to
    ) internal override {
        ICurvePlain256(POOL).exchange(tokenIndex[_tokenIn], tokenIndex[_tokenOut], _amountIn, _amountOut);
        // Confidently transfer amount-out
        _returnTo(_tokenOut, _amountOut, _to);
    }
}

//
//            __               ___ ___    __            _  
//           (_  | | |\/| |\/|  |   |    (_ \    / /\  |_) 
//           __) |_| |  | |  | _|_  |    __) \/\/ /--\ |   
//

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;

import "../interface/IDodoV1.sol";
import "../interface/IERC20.sol";
import "../lib/SafeERC20.sol";
import "../SummitAdapter.sol";

contract DodoV1Adapter is SummitAdapter {
    using SafeERC20 for IERC20;

    address public immutable HELPER;
    mapping(address => mapping(address => address)) tknsToPool; // base > quote > pool

    constructor(
        string memory _name,
        address[] memory _pools,
        address _helper,
        uint256 _gasEstimate
    ) SummitAdapter(_name, _gasEstimate) {
        _setPools(_pools, true);
        HELPER = _helper;
    }

    function setPools(address[] memory _pools, bool overwrite) external onlyMaintainer {
        _setPools(_pools, overwrite);
    }

    function _rmPools(address[] memory _pools) external onlyMaintainer {
        for (uint256 i; i < _pools.length; ++i) {
            (address baseTkn, address quoteTkn) = _getTknsForPool(_pools[i]);
            tknsToPool[baseTkn][quoteTkn] = address(0);
        }
    }

    function _setPools(address[] memory _pools, bool overwrite) internal {
        for (uint256 i; i < _pools.length; ++i) _setPool(_pools[i], overwrite);
    }

    function _setPool(address _pool, bool overwrite) internal {
        (address baseTkn, address quoteTkn) = _getTknsForPool(_pool);
        if (!overwrite) _overwriteCheck(baseTkn, quoteTkn, _pool);
        _approveTknsForPool(baseTkn, quoteTkn, _pool);
        tknsToPool[baseTkn][quoteTkn] = _pool;
    }

    function _getTknsForPool(address _pool) internal view returns (address baseToken, address quoteToken) {
        baseToken = IDodoV1(_pool)._BASE_TOKEN_();
        quoteToken = IDodoV1(_pool)._QUOTE_TOKEN_();
    }

    function _overwriteCheck(
        address baseTkn,
        address quoteTkn,
        address pool
    ) internal view {
        address existingPool = tknsToPool[baseTkn][quoteTkn];
        require(existingPool == address(0) || existingPool == pool, "Not allowed to overwrite");
    }

    function _approveTknsForPool(
        address _baseTkn,
        address _quoteTkn,
        address _pool
    ) internal {
        IERC20(_baseTkn).safeApprove(_pool, UINT_MAX);
        IERC20(_quoteTkn).safeApprove(_pool, UINT_MAX);
    }

    function _query(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut
    ) internal view override returns (uint256) {
        if (_amountIn == 0) return 0;
        address pool = tknsToPool[_tokenIn][_tokenOut];
        if (pool != address(0)) return IDodoV1(pool).querySellBaseToken(_amountIn);
        pool = tknsToPool[_tokenOut][_tokenIn];
        if (pool != address(0)) return IDodoHelper(HELPER).querySellQuoteToken(pool, _amountIn);
    }

    function _swap(
        uint256 _amountIn,
        uint256 _amountOut,
        address _tokenIn,
        address _tokenOut,
        address _to
    ) internal override {
        address pool = tknsToPool[_tokenIn][_tokenOut];
        if (pool != address(0)) IDodoV1(pool).sellBaseToken(_amountIn, _amountOut, "");
        pool = tknsToPool[_tokenOut][_tokenIn];
        if (pool != address(0)) IDodoV1(pool).buyBaseToken(_amountOut, _amountIn, "");
        _returnTo(_tokenOut, _amountOut, _to);
    }
}

//
//            __               ___ ___    __            _  
//           (_  | | |\/| |\/|  |   |    (_ \    / /\  |_) 
//           __) |_| |  | |  | _|_  |    __) \/\/ /--\ |   
//

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;

import "../interface/IDodoV2.sol";
import "../interface/IERC20.sol";
import "../lib/SafeERC20.sol";
import "../SummitAdapter.sol";

contract DodoV2Adapter is SummitAdapter {
    using SafeERC20 for IERC20;

    mapping(address => mapping(address => address)) public tknsToPool; // base > quote > pool

    constructor(
        string memory _name,
        address[] memory _pools,
        uint256 _gasEstimate
    ) SummitAdapter(_name, _gasEstimate) {
        _setPools(_pools, true);
    }

    function setPools(address[] memory _pools, bool overwrite) external onlyMaintainer {
        _setPools(_pools, overwrite);
    }

    function _rmPools(address[] memory _pools) external onlyMaintainer {
        for (uint256 i; i < _pools.length; ++i) {
            (address baseTkn, address quoteTkn) = _getTknsForPool(_pools[i]);
            tknsToPool[baseTkn][quoteTkn] = address(0);
        }
    }

    function _setPools(address[] memory _pools, bool overwrite) internal {
        for (uint256 i; i < _pools.length; ++i) _setPool(_pools[i], overwrite);
    }

    function _setPool(address _pool, bool overwrite) internal {
        (address baseTkn, address quoteTkn) = _getTknsForPool(_pool);
        if (!overwrite) _overwriteCheck(baseTkn, quoteTkn, _pool);
        tknsToPool[baseTkn][quoteTkn] = _pool;
    }

    function _getTknsForPool(address _pool) internal view returns (address baseToken, address quoteToken) {
        baseToken = IDodoV2(_pool)._BASE_TOKEN_();
        quoteToken = IDodoV2(_pool)._QUOTE_TOKEN_();
    }

    function _overwriteCheck(
        address baseTkn,
        address quoteTkn,
        address pool
    ) internal view {
        address existingPool = tknsToPool[baseTkn][quoteTkn];
        require(existingPool == address(0) || existingPool == pool, "Not allowed to overwrite");
    }

    function _query(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut
    ) internal view override returns (uint256 returnAmount) {
        if (_amountIn == 0) return 0;
        address pool = tknsToPool[_tokenIn][_tokenOut];
        if (pool != address(0)) return IDodoV2(pool).querySellBase(address(this), _amountIn);
        pool = tknsToPool[_tokenOut][_tokenIn];
        if (pool != address(0)) return IDodoV2(pool).querySellQuote(address(this), _amountIn);
    }

    function _swap(
        uint256 _amountIn,
        uint256 _amountOut,
        address _tokenIn,
        address _tokenOut,
        address _to
    ) internal override {
        uint256 returned = _dodoSwap(_amountIn, _tokenIn, _tokenOut);
        require(returned >= _amountOut, "Insufficient amount-out");
        _returnTo(_tokenOut, returned, _to);
    }

    function _dodoSwap(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut
    ) internal returns (uint256) {
        (function(address) external returns (uint256) fn, address pool) = _getPoolAndSwapFn(_tokenIn, _tokenOut);
        IERC20(_tokenIn).safeTransfer(pool, _amountIn);
        return fn(address(this));
    }

    function _getPoolAndSwapFn(address _tokenIn, address _tokenOut)
        internal
        view
        returns (function(address) external returns (uint256), address)
    {
        address pool = tknsToPool[_tokenIn][_tokenOut];
        if (pool != address(0)) return (IDodoV2(pool).sellBase, pool);
        pool = tknsToPool[_tokenOut][_tokenIn];
        if (pool != address(0)) return (IDodoV2(pool).sellQuote, pool);
        revert("Token pair not supported");
    }
}

//
//            __               ___ ___    __            _  
//           (_  | | |\/| |\/|  |   |    (_ \    / /\  |_) 
//           __) |_| |  | |  | _|_  |    __) \/\/ /--\ |   
//

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;

import "../interface/IUniswapFactory.sol";
import "../interface/IUniswapPair.sol";
import "../interface/IERC20.sol";
import "../lib/SafeERC20.sol";
import "../SummitAdapter.sol";

interface IDxSwapPair is IUniswapPair {
    function swapFee() external view returns (uint256);
}

contract DxSwapAdapter is SummitAdapter {
    using SafeERC20 for IERC20;

    uint256 internal constant FEE_DENOMINATOR = 1e4;
    address public immutable FACTORY;

    constructor(
        string memory _name,
        address _factory,
        uint256 _swapGasEstimate
    ) SummitAdapter(_name, _swapGasEstimate) {
        FACTORY = _factory;
    }

    function _getAmountOut(
        uint256 _amountIn,
        uint256 _reserveIn,
        uint256 _reserveOut,
        uint256 _fee
    ) internal pure returns (uint256 amountOut) {
        uint256 feeCompliment = FEE_DENOMINATOR - _fee;
        uint256 amountInWithFee = _amountIn * feeCompliment;
        uint256 numerator = amountInWithFee * _reserveOut;
        uint256 denominator = _reserveIn * FEE_DENOMINATOR + amountInWithFee;
        amountOut = numerator / denominator;
    }

    function _query(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut
    ) internal view override returns (uint256) {
        if (_tokenIn == _tokenOut || _amountIn == 0) {
            return 0;
        }
        address pair = IUniswapFactory(FACTORY).getPair(_tokenIn, _tokenOut);
        if (pair == address(0)) {
            return 0;
        }
        (uint256 r0, uint256 r1, ) = IUniswapPair(pair).getReserves();
        (uint256 reserveIn, uint256 reserveOut) = _tokenIn < _tokenOut ? (r0, r1) : (r1, r0);
        if (reserveIn > 0 && reserveOut > 0) {
            uint256 fee = IDxSwapPair(pair).swapFee();
            return _getAmountOut(_amountIn, reserveIn, reserveOut, fee);
        }
    }

    function _swap(
        uint256 _amountIn,
        uint256 _amountOut,
        address _tokenIn,
        address _tokenOut,
        address to
    ) internal override {
        address pair = IUniswapFactory(FACTORY).getPair(_tokenIn, _tokenOut);
        (uint256 amount0Out, uint256 amount1Out) = (_tokenIn < _tokenOut)
            ? (uint256(0), _amountOut)
            : (_amountOut, uint256(0));
        IERC20(_tokenIn).safeTransfer(pair, _amountIn);
        IUniswapPair(pair).swap(amount0Out, amount1Out, to, new bytes(0));
    }
}

//
//            __               ___ ___    __            _  
//           (_  | | |\/| |\/|  |   |    (_ \    / /\  |_) 
//           __) |_| |  | |  | _|_  |    __) \/\/ /--\ |   
//

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;

import "./UniswapV3AdapterBase.sol";

contract FusionAdapter is UniswapV3AdapterBase {

    constructor(
        string memory _name,
        uint256 _swapGasEstimate,
        uint256 _quoterGasLimit,
        address _quoter,
        address _factory,
        uint24[] memory _defaultFees
    ) UniswapV3AdapterBase(_name, _swapGasEstimate, _quoterGasLimit, _quoter, _factory, _defaultFees) {
    }

    function fusionXV3SwapCallback (
        int256 amount0Delta,
        int256 amount1Delta,
        bytes calldata
    ) external {
        if (amount0Delta > 0) {
            IERC20(IUniV3Pool(msg.sender).token0()).transfer(msg.sender, uint256(amount0Delta));
        } else {
            IERC20(IUniV3Pool(msg.sender).token1()).transfer(msg.sender, uint256(amount1Delta));
        }
    }
}

//
//            __               ___ ___    __            _  
//           (_  | | |\/| |\/|  |   |    (_ \    / /\  |_) 
//           __) |_| |  | |  | _|_  |    __) \/\/ /--\ |   
//

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;

import "../interface/IGeodePortal.sol";
import "../interface/IGeodeWP.sol";
import "../interface/IgAVAX.sol";
import "../interface/IERC20.sol";
import "../interface/IWETH.sol";
import "../SummitAdapter.sol";

contract GeodeWPAdapter is SummitAdapter {
    address internal constant WAVAX = 0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7;
    uint256 internal constant gAVAX_DENOMINATOR = 1e18;
    uint256 internal constant IGNORABLE_DEBT = 1e18;
    uint256 public immutable pooledTknId;
    address public immutable portal;
    address public immutable gavax;
    address public immutable pool;
    address public pooledTknInterface;

    constructor(
        string memory _name,
        address _portal,
        uint256 _pooledTknId,
        uint256 _swapGasEstimate
    ) SummitAdapter(_name, _swapGasEstimate) {
        pooledTknInterface = IGeodePortal(_portal).planetCurrentInterface(_pooledTknId);
        address _pool = IGeodePortal(_portal).planetWithdrawalPool(_pooledTknId);
        address _gavax = IGeodePortal(_portal).gAVAX();
        IgAVAX(_gavax).setApprovalForAll(_pool, true);
        pooledTknId = _pooledTknId;
        portal = _portal;
        gavax = _gavax;
        pool = _pool;
    }

    function setInterfaceForPooledTkn(address interfaceAddress) public onlyMaintainer {
        require(IgAVAX(gavax).isInterface(interfaceAddress, pooledTknId), "Not valid interface");
        pooledTknInterface = interfaceAddress;
    }

    function setGAvaxAllowance() public onlyMaintainer {
        IgAVAX(gavax).setApprovalForAll(pool, true);
    }

    function revokeGAvaxAllowance() public onlyMaintainer {
        IgAVAX(gavax).setApprovalForAll(pool, false);
    }

    function onERC1155Received(
        address,
        address,
        uint256,
        uint256,
        bytes memory
    ) public virtual returns (bytes4) {
        return this.onERC1155Received.selector;
    }

    function _query(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut
    ) internal view override returns (uint256 amountOut) {
        if (_amountIn == 0 || IGeodeWP(pool).paused()) {
            amountOut = 0;
        } else if (_tokenIn == WAVAX && _tokenOut == pooledTknInterface) {
            amountOut = _calcSwapAndMint(_amountIn);
        } else if (_tokenOut == WAVAX && _tokenIn == pooledTknInterface) {
            amountOut = _calcSwap(1, 0, _amountIn);
        }
    }

    function _calcSwapAndMint(uint256 amountIn) internal view returns (uint256) {
        uint256 debt = IGeodeWP(pool).getDebt();
        if (debt >= amountIn || _stakingPaused()) {
            // If pool is unbalanced and missing avax it's cheaper to swap
            return _calcSwap(0, 1, amountIn);
        } else {
            // Swap debt and mint the rest
            uint256 amountOutBought;
            if (debt > IGNORABLE_DEBT) {
                amountOutBought = _calcSwap(0, 1, debt);
                amountIn -= debt;
            }
            uint256 amountOutMinted = _calcMint(amountIn);
            return amountOutBought + amountOutMinted;
        }
    }

    function _stakingPaused() internal view returns (bool) {
        return IGeodePortal(portal).isStakingPausedForPool(pooledTknId);
    }

    function _calcSwap(
        uint8 tknInIndex,
        uint8 tknOutIndex,
        uint256 amountIn
    ) internal view returns (uint256) {
        try IGeodeWP(pool).calculateSwap(tknInIndex, tknOutIndex, amountIn) returns (uint256 amountOut) {
            return amountOut;
        } catch {
            return 0;
        }
    }

    function _calcMint(uint256 amountIn) internal view returns (uint256) {
        uint256 price = IgAVAX(gavax).pricePerShare(pooledTknId);
        return (amountIn * gAVAX_DENOMINATOR) / price;
    }

    function _swap(
        uint256 _amountIn,
        uint256 _amountOut,
        address _tokenIn,
        address _tokenOut,
        address _to
    ) internal override {
        if (_tokenIn == WAVAX) {
            IWETH(WAVAX).withdraw(_amountIn);
            if (_stakingPaused()) {
                _swapUnderlying(0, 1, _amountIn, _amountOut, _amountIn);
            } else {
                _geodeStake(_amountIn, _amountOut);
            }
        } else {
            _swapUnderlying(1, 0, _amountIn, _amountOut, 0);
            IWETH(WAVAX).deposit{ value: address(this).balance }();
        }
        uint256 balThis = IERC20(_tokenOut).balanceOf(address(this));
        require(balThis >= _amountOut, "Insufficient amount out");
        _returnTo(_tokenOut, balThis, _to);
    }

    function _swapUnderlying(
        uint8 _tokenInIndex,
        uint8 _tokenOutIndex,
        uint256 _amountIn,
        uint256 _amountOut,
        uint256 _val
    ) internal {
        IGeodeWP(pool).swap{ value: _val }(_tokenInIndex, _tokenOutIndex, _amountIn, _amountOut, block.timestamp);
    }

    function _geodeStake(uint256 _amountIn, uint256 _amountOut) internal {
        IGeodePortal(portal).stake{ value: _amountIn }(pooledTknId, _amountOut, block.timestamp);
    }
}

//
//            __               ___ ___    __            _  
//           (_  | | |\/| |\/|  |   |    (_ \    / /\  |_) 
//           __) |_| |  | |  | _|_  |    __) \/\/ /--\ |   
//

// SPDX-License-Identifier: GPL-3.0-only

pragma solidity ^0.8.0;

import "../interface/IGmxVault.sol";
import "../interface/IERC20.sol";
import "../lib/SafeERC20.sol";
import "../SummitAdapter.sol";

contract GmxAdapter is SummitAdapter {
    using SafeERC20 for IERC20;

    uint256 public constant BASIS_POINTS_DIVISOR = 1e4;
    uint256 public constant PRICE_PRECISION = 1e30;
    uint256 public constant USDG_DECIMALS = 18;
    address public immutable VAULT;
    bool immutable USE_VAULT_UTILS;
    address immutable USDG;
    mapping(address => bool) public isPoolTkn; // unwanted tkns can be ignored by adapter
    mapping(address => uint256) tokenDecimals;

    constructor(
        string memory _name,
        address _vault,
        uint256 _swapGasEstimate
    ) SummitAdapter(_name, _swapGasEstimate) {
        _setVaultTkns(_vault);
        USE_VAULT_UTILS = _vaultHasUtils(_vault);
        USDG = IGmxVault(_vault).usdg();
        VAULT = _vault;
    }

    //                                 UTILS                                  \\

    function addPoolTkns(address[] calldata _tokens) external onlyMaintainer {
        for (uint256 i; i < _tokens.length; ++i) _setToken(_tokens[i]);
    }

    function rmPoolTkns(address[] calldata _tokens) external onlyMaintainer {
        for (uint256 i; i < _tokens.length; ++i) isPoolTkn[_tokens[i]] = false;
    }

    function _setVaultTkns(address _vault) internal {
        uint256 whitelistedTknsLen = IGmxVault(_vault).allWhitelistedTokensLength();
        for (uint256 i = 0; i < whitelistedTknsLen; i++) {
            address token = IGmxVault(_vault).allWhitelistedTokens(i);
            _setToken(token);
        }
    }

    function _setToken(address _token) internal {
        tokenDecimals[_token] = IERC20(_token).decimals();
        isPoolTkn[_token] = true;
    }

    function _vaultHasUtils(address _vault) internal view returns (bool) {
        try IGmxVault(_vault).vaultUtils() {
            return true;
        } catch {
            return false;
        }
    }

    //                                 QUERY                                  \\

    function _query(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut
    ) internal view override returns (uint256) {
        if (_validArgs(_amountIn, _tokenIn, _tokenOut)) return _getAmountOut(_amountIn, _tokenIn, _tokenOut);
    }

    function _validArgs(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut
    ) internal view returns (bool) {
        return
            _amountIn != 0 &&
            _tokenIn != _tokenOut &&
            isPoolTkn[_tokenIn] &&
            IGmxVault(VAULT).whitelistedTokens(_tokenIn) &&
            IGmxVault(VAULT).whitelistedTokens(_tokenOut) &&
            IGmxVault(VAULT).isSwapEnabled() &&
            _hasVaultEnoughBal(_tokenIn, 1); // Prevents calc problems
    }

    function _getAmountOut(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut
    ) internal view returns (uint256) {
        (uint256 amountOut, uint256 usdgAmount) = _getGrossAmountOutAndUsdg(_amountIn, _tokenIn, _tokenOut);
        return _calcNetAmountOut(_tokenIn, _tokenOut, amountOut, usdgAmount);
    }

    function _calcNetAmountOut(
        address _tokenIn,
        address _tokenOut,
        uint256 _amountOut,
        uint256 _usdgAmount
    ) internal view returns (uint256) {
        uint256 feeBps = _getFeeBasisPoint(_tokenIn, _tokenOut, _usdgAmount);
        uint256 netAmountOut = _amountOutAfterFees(_amountOut, feeBps);
        bool withinVaultLimits = _isWithinVaultLimits(_tokenIn, _tokenOut, _usdgAmount, netAmountOut);
        if (withinVaultLimits) return netAmountOut;
    }

    function _getGrossAmountOutAndUsdg(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut
    ) internal view returns (uint256 amountOut, uint256 usdgAmount) {
        (uint256 priceIn, uint256 priceOut) = _getPrices(_tokenIn, _tokenOut);
        amountOut = (_amountIn * priceIn) / priceOut;
        amountOut = _adjustForDecimals(amountOut, _tokenIn, _tokenOut);
        usdgAmount = _getUsdgAmount(_amountIn, priceIn, _tokenIn);
    }

    function _getUsdgAmount(
        uint256 _amountIn,
        uint256 _priceIn,
        address _tokenIn
    ) internal view returns (uint256 usdgAmount) {
        usdgAmount = (_amountIn * _priceIn) / PRICE_PRECISION;
        usdgAmount = _adjustForDecimals(usdgAmount, _tokenIn, USDG);
    }

    function _amountOutAfterFees(uint256 _amountOut, uint256 _feeBasisPoints) internal pure returns (uint256) {
        return (_amountOut * (BASIS_POINTS_DIVISOR - _feeBasisPoints)) / BASIS_POINTS_DIVISOR;
    }

    function _adjustForDecimals(
        uint256 _amount,
        address _tokenDiv,
        address _tokenMul
    ) internal view returns (uint256) {
        uint256 decimalsDiv = _tokenDiv == USDG ? USDG_DECIMALS : tokenDecimals[_tokenDiv];
        uint256 decimalsMul = _tokenMul == USDG ? USDG_DECIMALS : tokenDecimals[_tokenMul];
        return (_amount * 10**decimalsMul) / 10**decimalsDiv;
    }

    function _getPrices(address _tokenIn, address _tokenOut) internal view returns (uint256 priceIn, uint256 priceOut) {
        IGmxVaultPriceFeed priceFeed = IGmxVault(VAULT).priceFeed();
        priceIn = priceFeed.getPrice(_tokenIn, false, true, true);
        priceOut = priceFeed.getPrice(_tokenOut, true, true, true);
    }

    function _hasVaultEnoughBal(address _token, uint256 _amount) private view returns (bool) {
        return IERC20(_token).balanceOf(VAULT) >= _amount;
    }

    function _isWithinVaultLimits(
        address _tokenIn,
        address _tokenOut,
        uint256 _amountInUsdg,
        uint256 _amountOut
    ) private view returns (bool) {
        uint256 poolBalTknOut = IGmxVault(VAULT).poolAmounts(_tokenOut);
        if (poolBalTknOut < _amountOut) return false;
        uint256 newPoolBalTknOut = poolBalTknOut - _amountOut;
        return
            !reservedAmountExceeded(newPoolBalTknOut, _tokenOut) &&
            !bufferAmountExceeded(newPoolBalTknOut, _tokenOut) &&
            !maxDebtExceeded(_amountInUsdg, _tokenIn);
    }

    function reservedAmountExceeded(uint256 _newPoolBalTknOut, address _tokenOut) internal view returns (bool) {
        uint256 reservedAmount = IGmxVault(VAULT).reservedAmounts(_tokenOut);
        return _newPoolBalTknOut < reservedAmount;
    }

    function bufferAmountExceeded(uint256 _newPoolBalTknOut, address _tokenOut) internal view returns (bool) {
        uint256 bufferAmount = IGmxVault(VAULT).bufferAmounts(_tokenOut);
        return _newPoolBalTknOut < bufferAmount;
    }

    function maxDebtExceeded(uint256 _amountInUsdg, address _tokenIn) internal view returns (bool) {
        uint256 maxUsdgAmount = IGmxVault(VAULT).maxUsdgAmounts(_tokenIn);
        if (maxUsdgAmount == 0) return false;
        uint256 newUsdgAmount = IGmxVault(VAULT).usdgAmounts(_tokenIn) + _amountInUsdg;
        return newUsdgAmount > maxUsdgAmount;
    }

    function _getFeeBasisPoint(
        address _tokenIn,
        address _tokenOut,
        uint256 usdgAmount
    ) internal view returns (uint256) {
        if (USE_VAULT_UTILS)
            return IGmxVault(VAULT).vaultUtils().getSwapFeeBasisPoints(_tokenIn, _tokenOut, usdgAmount);
        return _calcFeeBasisPoints(_tokenIn, _tokenOut, usdgAmount);
    }

    function _calcFeeBasisPoints(
        address _tokenIn,
        address _tokenOut,
        uint256 usdgAmount
    ) internal view returns (uint256 feeBasisPoints) {
        bool isStableSwap = IGmxVault(VAULT).stableTokens(_tokenIn) && IGmxVault(VAULT).stableTokens(_tokenOut);
        uint256 baseBps = isStableSwap
            ? IGmxVault(VAULT).stableSwapFeeBasisPoints()
            : IGmxVault(VAULT).swapFeeBasisPoints();
        uint256 taxBps = isStableSwap ? IGmxVault(VAULT).stableTaxBasisPoints() : IGmxVault(VAULT).taxBasisPoints();
        uint256 feesBasisPoints0 = IGmxVault(VAULT).getFeeBasisPoints(_tokenIn, usdgAmount, baseBps, taxBps, true);
        uint256 feesBasisPoints1 = IGmxVault(VAULT).getFeeBasisPoints(_tokenOut, usdgAmount, baseBps, taxBps, false);
        // use the higher of the two fee basis points
        feeBasisPoints = feesBasisPoints0 > feesBasisPoints1 ? feesBasisPoints0 : feesBasisPoints1;
    }

    //                                  SWAP                                  \\

    function _swap(
        uint256 _amountIn,
        uint256 _amountOut,
        address _tokenIn,
        address _tokenOut,
        address _to
    ) internal override {
        IERC20(_tokenIn).safeTransfer(VAULT, _amountIn);
        IGmxVault(VAULT).swap(
            _tokenIn,
            _tokenOut,
            address(this) // No check for amount-out within swap function
        );
        // Confidently transfer amount-out
        _returnTo(_tokenOut, _amountOut, _to);
    }
}

//
//            __               ___ ___    __            _  
//           (_  | | |\/| |\/|  |   |    (_ \    / /\  |_) 
//           __) |_| |  | |  | _|_  |    __) \/\/ /--\ |   
//

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;

import "../interface/IKyberPool.sol";
import "../interface/IERC20.sol";
import "../lib/SafeERC20.sol";
import "../SummitAdapter.sol";

contract KyberAdapter is SummitAdapter {
    using SafeERC20 for IERC20;

    uint256 public constant PRECISION = 1e18;
    mapping(address => mapping(address => address)) internal TKNS_TO_POOL;

    constructor(
        string memory _name,
        address[] memory _pools,
        uint256 _swapGasEstimate
    ) SummitAdapter(_name, _swapGasEstimate) {
        addPools(_pools);
    }

    function addPools(address[] memory _pools) public onlyMaintainer {
        // Note: Overrides existing if pool has same tkns but different APR
        for (uint256 i = 0; i < _pools.length; i++) {
            address tkn0 = IKyberPool(_pools[i]).token0();
            address tkn1 = IKyberPool(_pools[i]).token1();
            TKNS_TO_POOL[tkn0][tkn1] = _pools[i];
            TKNS_TO_POOL[tkn1][tkn0] = _pools[i];
        }
    }

    function removePools(address[] memory _pools) public onlyMaintainer {
        // Note: Overrides existing if pool has same tkns but different APR
        for (uint256 i = 0; i < _pools.length; i++) {
            address tkn0 = IKyberPool(_pools[i]).token0();
            address tkn1 = IKyberPool(_pools[i]).token1();
            TKNS_TO_POOL[tkn0][tkn1] = address(0);
            TKNS_TO_POOL[tkn1][tkn0] = address(0);
        }
    }

    function getPool(address tkn0, address tkn1) public view returns (address) {
        return TKNS_TO_POOL[tkn0][tkn1];
    }

    function _getAmountOut(
        uint256 amountIn,
        uint256 vReserveIn,
        uint256 vReserveOut,
        uint256 feeInPrecision
    ) internal pure returns (uint256 amountOut) {
        // Based on https://github.com/dynamic-amm/smart-contracts/blob/master/contracts/libraries/DMMLibrary.sol
        uint256 amountInWithFee = (amountIn * (PRECISION - feeInPrecision)) / PRECISION;
        uint256 numerator = amountInWithFee * vReserveOut;
        uint256 denominator = vReserveIn + amountInWithFee;
        amountOut = numerator / denominator;
    }

    function _query(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut
    ) internal view override returns (uint256 amountOut) {
        if (_tokenIn == _tokenOut || _amountIn == 0) {
            return 0;
        }
        address pool = getPool(_tokenIn, _tokenOut);
        if (pool == address(0)) {
            return 0;
        }
        (uint112 r0, uint112 r1, uint112 vr0, uint112 vr1, uint256 feeInPrecision) = IKyberPool(pool).getTradeInfo();
        (uint112 reserveIn, uint112 reserveOut) = _tokenIn < _tokenOut ? (r0, r1) : (r1, r0);
        (uint112 vReserveIn, uint112 vReserveOut) = _tokenIn < _tokenOut ? (vr0, vr1) : (vr1, vr0);
        if (reserveIn > 0 && reserveOut > 0) {
            uint256 _amountOut = _getAmountOut(_amountIn, vReserveIn, vReserveOut, feeInPrecision);
            if (reserveOut > amountOut) amountOut = _amountOut;
        }
    }

    function _swap(
        uint256 _amountIn,
        uint256 _amountOut,
        address _tokenIn,
        address _tokenOut,
        address to
    ) internal override {
        address pair = getPool(_tokenIn, _tokenOut);
        (uint256 amount0Out, uint256 amount1Out) = (_tokenIn < _tokenOut)
            ? (uint256(0), _amountOut)
            : (_amountOut, uint256(0));
        IERC20(_tokenIn).safeTransfer(pair, _amountIn);
        IKyberPool(pair).swap(amount0Out, amount1Out, to, new bytes(0));
    }
}

//
//            __               ___ ___    __            _  
//           (_  | | |\/| |\/|  |   |    (_ \    / /\  |_) 
//           __) |_| |  | |  | _|_  |    __) \/\/ /--\ |   
//

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;

import "./UniswapV3likeAdapter.sol";

interface IKyberPool {
    function token0() external view returns (address);

    function token1() external view returns (address);

    function swap(
        address recipient,
        int256 swapQty,
        bool isToken0,
        uint160 limitSqrtP,
        bytes calldata data
    ) external returns (int256 qty0, int256 qty1);
}

contract KyberElasticAdapter is UniswapV3likeAdapter {
    using SafeERC20 for IERC20;

    mapping(address => mapping(address => address)) public tknsToPoolWL;

    constructor(
        string memory _name,
        uint256 _swapGasEstimate,
        uint256 _quoterGasLimit,
        address _quoter,
        address[] memory _whitelistedPools
    ) UniswapV3likeAdapter(_name, _swapGasEstimate, _quoter, _quoterGasLimit) {
        addPoolsToWL(_whitelistedPools);
    }

    function addPoolsToWL(address[] memory pools) public onlyMaintainer {
        for (uint256 i; i < pools.length; ++i) addPoolToWL(pools[i]);
    }

    function rmPoolsFromWL(address[] memory pools) external onlyMaintainer {
        for (uint256 i; i < pools.length; ++i) rmPoolFromWL(pools[i]);
    }

    function addPoolToWL(address pool) internal {
        address t0 = IKyberPool(pool).token0();
        address t1 = IKyberPool(pool).token1();
        tknsToPoolWL[t0][t1] = pool;
        tknsToPoolWL[t1][t0] = pool;
    }

    function rmPoolFromWL(address pool) internal {
        address t0 = IKyberPool(pool).token0();
        address t1 = IKyberPool(pool).token1();
        tknsToPoolWL[t0][t1] = address(0);
        tknsToPoolWL[t1][t0] = address(0);
    }

    function _underlyingSwap(
        QParams memory params, 
        bytes memory callbackData
    ) internal override returns (uint256) {
        address pool = getBestPool(params.tokenIn, params.tokenOut);
        (bool zeroForOne, uint160 sqrtPriceLimitX96) = 
            getZeroOneAndSqrtPriceLimitX96(params.tokenIn, params.tokenOut);
        (int256 amount0, int256 amount1) = IKyberPool(pool).swap(
            address(this),
            int256(params.amountIn),
            zeroForOne,
            sqrtPriceLimitX96,
            callbackData
        );
        return zeroForOne ? uint256(-amount1) : uint256(-amount0);
    }

    function getBestPool(
        address token0, 
        address token1
    ) internal view override returns (address) {
        return tknsToPoolWL[token0][token1];
    }

    function swapCallback(
        int256 amount0Delta,
        int256 amount1Delta,
        bytes calldata
    ) external {
        if (amount0Delta > 0) {
            IERC20(IKyberPool(msg.sender).token0()).transfer(msg.sender, uint256(amount0Delta));
        } else {
            IERC20(IKyberPool(msg.sender).token1()).transfer(msg.sender, uint256(amount1Delta));
        }
    }
}

//
//            __               ___ ___    __            _  
//           (_  | | |\/| |\/|  |   |    (_ \    / /\  |_) 
//           __) |_| |  | |  | _|_  |    __) \/\/ /--\ |   
//

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;


import "../SummitAdapter.sol";
import "../interface/IERC20.sol";
import "../lib/SafeERC20.sol";
import "../interface/ILBFactory.sol";
import "../interface/ILB2Pair.sol";

struct LBQuote {
    uint256 amountOut;
    address pair;
    bool swapForY;
}

contract LB2Adapter is SummitAdapter {
    using SafeERC20 for IERC20;

    address public immutable FACTORY;
    bool public allowIgnoredPairs = true;
    bool public allowExternalPairs = true;
    uint256 public quoteGasLimit = 600_000;

    constructor(
        string memory _name,
        uint256 _swapGasEstimate,
        uint256 _quoteGasLimit,
        address _factory
    ) SummitAdapter(_name, _swapGasEstimate) {
        setQuoteGasLimit(_quoteGasLimit);
        FACTORY = _factory;
    }

    function setAllowIgnoredPairs(bool _allowIgnoredPairs) external onlyMaintainer {
        allowIgnoredPairs = _allowIgnoredPairs;
    }

    function setAllowExternalPairs(bool _allowExternalPairs) external onlyMaintainer {
        allowExternalPairs = _allowExternalPairs;
    }

    function setQuoteGasLimit(uint256 _quoteGasLimit) public onlyMaintainer {
        quoteGasLimit = _quoteGasLimit;
    }

    function _query(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut
    ) internal view override returns (uint256 amountOut) {
        (amountOut, , ) = _getBestQuote(_amountIn, _tokenIn, _tokenOut);
    }

    function _swap(
        uint256 _amountIn,
        uint256 _minAmountOut,
        address _tokenIn,
        address _tokenOut,
        address to
    ) internal override {
        (uint256 amountOut, address pair, bool swapForY) = _getBestQuote(_amountIn, _tokenIn, _tokenOut);
        require(amountOut >= _minAmountOut, "LBAdapter: insufficient amountOut received");
        IERC20(_tokenIn).transfer(pair, _amountIn);
        ILBPair(pair).swap(swapForY, to);
    }

    function _getBestQuote(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut
    )
        internal
        view
        returns (
            uint256 amountOut,
            address pair,
            bool swapForY
        )
    {
        ILBFactory.LBPairInformation[] memory LBPairsAvailable = ILBFactory(FACTORY).getAllLBPairs(_tokenIn, _tokenOut);

        if (LBPairsAvailable.length > 0 && _amountIn > 0) {
            for (uint256 i; i < LBPairsAvailable.length; ++i) {
                if (!LBPairsAvailable[i].ignoredForRouting && !allowIgnoredPairs) {
                    continue;
                }
                if (!LBPairsAvailable[i].createdByOwner && !allowExternalPairs) {
                    continue;
                }

                swapForY = ILBPair(LBPairsAvailable[i].LBPair).getTokenY() == _tokenOut;
                uint256 swapAmountOut = getQuote(LBPairsAvailable[i].LBPair, _amountIn, swapForY);

                if (swapAmountOut > amountOut) {
                    amountOut = swapAmountOut;
                    pair = LBPairsAvailable[i].LBPair;
                }
            }
        }
    }

    function getQuote(
        address pair,
        uint256 amountIn,
        bool swapForY
    ) internal view returns (uint256 out) {
        try ILBPair(pair).getSwapOut{gas: quoteGasLimit}(
            uint128(amountIn), 
            swapForY
        ) returns (uint128 amountInLeft, uint128 amountOut, uint128) {
            if (amountInLeft == 0) {
                out = amountOut;
            }
        } catch {}
    }
}

//
//            __               ___ ___    __            _  
//           (_  | | |\/| |\/|  |   |    (_ \    / /\  |_) 
//           __) |_| |  | |  | _|_  |    __) \/\/ /--\ |   
//

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;

import "../SummitAdapter.sol";
import "../interface/IERC20.sol";
import "../lib/SafeERC20.sol";
import "../interface/ILBRouter.sol";
import "../interface/ILBFactory.sol";
import "../interface/ILBPair.sol";

struct LBQuote {
    uint256 amountOut;
    address pair;
    bool swapForY;
}

contract LBAdapter is SummitAdapter {
    using SafeERC20 for IERC20;

    address public immutable FACTORY;
    address public immutable ROUTER;
    bool public allowIgnoredPairs = true;
    bool public allowExternalPairs = true;
    uint256 public quoteGasLimit = 600_000;

    constructor(
        string memory _name,
        uint256 _swapGasEstimate,
        address _router
    ) SummitAdapter(_name, _swapGasEstimate) {
        FACTORY = ILBRouter(_router).factory();
        ROUTER = _router;
    }

    function setAllowIgnoredPairs(bool _allowIgnoredPairs) external onlyMaintainer {
        allowIgnoredPairs = _allowIgnoredPairs;
    }

    function setAllowExternalPairs(bool _allowExternalPairs) external onlyMaintainer {
        allowExternalPairs = _allowExternalPairs;
    }

    function setQuoteGasLimit(uint256 _quoteGasLimit) external onlyMaintainer {
        quoteGasLimit = _quoteGasLimit;
    }

    function _query(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut
    ) internal view override returns (uint256 amountOut) {
        (amountOut, , ) = _getBestQuote(_amountIn, _tokenIn, _tokenOut);
    }

    function _swap(
        uint256 _amountIn,
        uint256 _minAmountOut,
        address _tokenIn,
        address _tokenOut,
        address to
    ) internal override {
        (uint256 amountOut, address pair, bool swapForY) = _getBestQuote(_amountIn, _tokenIn, _tokenOut);
        require(amountOut >= _minAmountOut, "LBAdapter: insufficient amountOut received");
        IERC20(_tokenIn).transfer(pair, _amountIn);
        ILBPair(pair).swap(swapForY, to);
    }

    function _getBestQuote(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut
    )
        internal
        view
        returns (
            uint256 amountOut,
            address pair,
            bool swapForY
        )
    {
        ILBFactory.LBPairInformation[] memory LBPairsAvailable = ILBFactory(FACTORY).getAllLBPairs(_tokenIn, _tokenOut);

        if (LBPairsAvailable.length > 0 && _amountIn > 0) {
            for (uint256 i; i < LBPairsAvailable.length; ++i) {
                if (!LBPairsAvailable[i].ignoredForRouting && !allowIgnoredPairs) {
                    continue;
                }
                if (!LBPairsAvailable[i].createdByOwner && !allowExternalPairs) {
                    continue;
                }

                swapForY = ILBPair(LBPairsAvailable[i].LBPair).tokenY() == _tokenOut;
                uint256 swapAmountOut = getQuote(LBPairsAvailable[i].LBPair, _amountIn, swapForY);

                if (swapAmountOut > amountOut) {
                    amountOut = swapAmountOut;
                    pair = LBPairsAvailable[i].LBPair;
                }
            }
        }
    }

    function getQuote(
        address pair,
        uint256 amountIn,
        bool swapForY
    ) internal view returns (uint256 amountOut) {
        bytes memory calldata_ = abi.encodeWithSignature("getSwapOut(address,uint256,bool)", pair, amountIn, swapForY);
        (bool success, bytes memory data) = ROUTER.staticcall{ gas: quoteGasLimit }(calldata_);
        if (success)
            assembly {
                amountOut := mload(add(data, 0x20))
            }
    }
}

//
//            __               ___ ___    __            _  
//           (_  | | |\/| |\/|  |   |    (_ \    / /\  |_) 
//           __) |_| |  | |  | _|_  |    __) \/\/ /--\ |   
//

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;

import "../interface/ImSUMMIT.sol";
import "../lib/SafeERC20.sol";
import "../SummitAdapter.sol";

contract MiniSummitAdapter is SummitAdapter {
    using SafeERC20 for IERC20;

    address public constant SUMMIT = 0x59414b3089ce2AF0010e7523Dea7E2b35d776ec7;
    address public constant mSUMMIT = 0xdDAaAD7366B455AfF8E7c82940C43CEB5829B604;

    constructor(uint256 _swapGasEstimate) SummitAdapter("MiniSummitAdapter", _swapGasEstimate) {
        setAllowances();
    }

    function setAllowances() internal {
        IERC20(mSUMMIT).safeApprove(mSUMMIT, UINT_MAX);
        IERC20(SUMMIT).safeApprove(mSUMMIT, UINT_MAX);
    }

    function _query(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut
    ) internal pure override returns (uint256 amountOut) {
        if ((_tokenIn == mSUMMIT && _tokenOut == SUMMIT) || (_tokenIn == SUMMIT && _tokenOut == mSUMMIT)) {
            amountOut = _amountIn;
        }
    }

    function _swap(
        uint256 _amountIn,
        uint256,
        address _tokenIn,
        address _tokenOut,
        address _to
    ) internal override {
        if (_tokenIn == mSUMMIT && _tokenOut == SUMMIT) {
            ImSUMMIT(mSUMMIT).unmoon(_amountIn, _to);
        } else if (_tokenIn == SUMMIT && _tokenOut == mSUMMIT) {
            ImSUMMIT(mSUMMIT).moon(_amountIn, _to);
        } else {
            revert("Unsupported token");
        }
    }
}

//
//            __               ___ ___    __            _  
//           (_  | | |\/| |\/|  |   |    (_ \    / /\  |_) 
//           __) |_| |  | |  | _|_  |    __) \/\/ /--\ |   
//
//                              ,=.
//                ,=""""==.__.="  o".___
//          ,=.=="                  ___/
//    ,==.,"    ,          , \,===""
//   <     ,==)  \"'"=._.==)  \
//    `==''    `"           `"
//

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;

import "../interface/IPlatypus.sol";
import "../interface/IERC20.sol";
import "../interface/IWETH.sol";
import "../lib/SafeERC20.sol";
import "../SummitAdapter.sol";

contract PlatypusAdapter is SummitAdapter {
    using SafeERC20 for IERC20;

    event PartialPoolSupport(address pool, address[] tkns);
    event AddPoolSupport(address pool);
    event RmPoolSupport(address pool);

    mapping(address => mapping(address => address)) private tknToTknToPool;

    constructor(
        string memory _name,
        uint256 _swapGasEstimate,
        address[] memory _initPools
    ) SummitAdapter(_name, _swapGasEstimate) {
        addPools(_initPools);
    }

    function getPoolForTkns(address tknIn, address tknOut) public view returns (address) {
        return tknToTknToPool[tknIn][tknOut];
    }

    function _approveIfNeeded(address tkn, address spender) internal {
        uint256 allowance = IERC20(tkn).allowance(address(this), spender);
        if (allowance < UINT_MAX) {
            IERC20(tkn).approve(spender, UINT_MAX);
        }
    }

    // @dev Returns false if repeated tkns
    function _poolSupportsTkns(address pool, address[] memory tkns) internal view returns (bool) {
        address[] memory supportedTkns = IPlatypus(pool).getTokenAddresses();
        uint256 supportedCount;
        for (uint256 i = 0; i < supportedTkns.length; i++) {
            for (uint256 j = 0; j < tkns.length; j++) {
                if (supportedTkns[i] == tkns[j]) {
                    supportedCount++;
                    break;
                }
            }
        }
        return supportedCount == tkns.length;
    }

    function _setPoolForTkns(address[] memory tkns, address pool) internal {
        for (uint256 i = 0; i < tkns.length; i++) {
            for (uint256 j = 0; j < tkns.length; j++) {
                if (i != j) {
                    tknToTknToPool[tkns[i]][tkns[j]] = pool;
                    if (pool != address(0)) {
                        _approveIfNeeded(tkns[i], pool);
                    }
                }
            }
        }
    }

    function addPools(address[] memory pools) public onlyMaintainer {
        for (uint256 i = 0; i < pools.length; i++) {
            address pool = pools[i];
            address[] memory supportedTkns = IPlatypus(pool).getTokenAddresses();
            _setPoolForTkns(supportedTkns, pool);
            emit AddPoolSupport(pool);
        }
    }

    function setPoolForTkns(address pool, address[] memory tkns) external onlyMaintainer {
        require(tkns.length > 1, "At least two tkns");
        require(pool != address(0), "Only non-zero pool");
        require(_poolSupportsTkns(pool, tkns), "Pool does not support tkns");
        // Assume above checks there is no repeats
        _setPoolForTkns(tkns, pool);
        emit PartialPoolSupport(pool, tkns);
    }

    function rmPools(address[] calldata pools) external onlyMaintainer {
        for (uint256 i = 0; i < pools.length; i++) {
            address pool = pools[i];
            address[] memory supportedTkns = IPlatypus(pool).getTokenAddresses();
            _setPoolForTkns(supportedTkns, address(0));
            emit RmPoolSupport(pool);
        }
    }

    function _query(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut
    ) internal view override returns (uint256) {
        address pool = getPoolForTkns(_tokenIn, _tokenOut);
        if (pool == address(0) || _amountIn == 0 || IPlatypus(pool).paused()) {
            return 0;
        }
        try IPlatypus(pool).quotePotentialSwap(_tokenIn, _tokenOut, _amountIn) returns (uint256 amountOut) {
            return amountOut;
        } catch {
            return 0;
        }
    }

    function _swap(
        uint256 _amountIn,
        uint256 _amountOut,
        address _tokenIn,
        address _tokenOut,
        address _to
    ) internal override {
        address pool = getPoolForTkns(_tokenIn, _tokenOut);
        IPlatypus(pool).swap(_tokenIn, _tokenOut, _amountIn, _amountOut, _to, block.timestamp);
    }
}

//
//            __               ___ ___    __            _  
//           (_  | | |\/| |\/|  |   |    (_ \    / /\  |_) 
//           __) |_| |  | |  | _|_  |    __) \/\/ /--\ |   
//

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;

import "./UniswapV3likeAdapter.sol";

interface IUniV3Factory {
    function feeAmountTickSpacing(uint24) external view returns (int24);

    function getPool(
        address,
        address,
        uint24
    ) external view returns (address);
}

contract RamsesV2Adapter is UniswapV3likeAdapter {
    using SafeERC20 for IERC20;

    address immutable FACTORY;
    mapping(uint24 => bool) public isFeeAmountEnabled;
    uint24[] public feeAmounts;

    constructor(
        string memory _name,
        uint256 _swapGasEstimate,
        uint256 _quoterGasLimit,
        address _quoter,
        address _factory,
        uint24[] memory _defaultFees
    ) UniswapV3likeAdapter(_name, _swapGasEstimate, _quoter, _quoterGasLimit) {
        FACTORY = _factory;
        for (uint i = 0; i < _defaultFees.length; i++) {
            addFeeAmount(_defaultFees[i]);
        }
    }

    function enableFeeAmounts(uint24[] calldata _amounts) external onlyMaintainer {
        for (uint256 i; i < _amounts.length; ++i) enableFeeAmount(_amounts[i]);
    }

    function enableFeeAmount(uint24 _fee) internal {
        require(!isFeeAmountEnabled[_fee], "Fee already enabled");
        if (IUniV3Factory(FACTORY).feeAmountTickSpacing(_fee) == 0)
            revert("Factory doesn't support fee");
        addFeeAmount(_fee);
    }

    function addFeeAmount(uint24 _fee) internal {
        isFeeAmountEnabled[_fee] = true;
        feeAmounts.push(_fee);
    }

    function getBestPool(
        address token0, 
        address token1
    ) internal view override returns (address mostLiquid) {
        uint128 deepestLiquidity;
        for (uint256 i; i < feeAmounts.length; ++i) {
            address pool = IUniV3Factory(FACTORY).getPool(token0, token1, feeAmounts[i]);
            if (pool == address(0))
                continue;
            uint128 liquidity = IUniV3Pool(pool).liquidity();
            if (liquidity > deepestLiquidity) {
                deepestLiquidity = liquidity;
                mostLiquid = pool;
            }
        }
    }

    function ramsesV2SwapCallback(
        int256 amount0Delta,
        int256 amount1Delta,
        bytes calldata
    ) external {
        if (amount0Delta > 0) {
            IERC20(IUniV3Pool(msg.sender).token0()).transfer(msg.sender, uint256(amount0Delta));
        } else {
            IERC20(IUniV3Pool(msg.sender).token1()).transfer(msg.sender, uint256(amount1Delta));
        }
    }
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import { SummitAdapter, IERC20, SafeERC20 } from "../SummitAdapter.sol";
import { IGenericFactory } from "../interface/IGenericFactory.sol";
import { IQuoter } from "../interface/IReservoirQuoter.sol";
import { IReservoirPair } from "../interface/IReservoirPair.sol";

contract ReservoirAdapter is SummitAdapter {
    using SafeERC20 for IERC20;

    uint256 internal constant FEE_ACCURACY = 1_000_000;

    IGenericFactory public immutable factory;
    IQuoter public immutable quoter;

    constructor(
        string memory _name,
        address _factory,
        address _quoter,
        uint256 _swapGasEstimate // we use the worse off i.e. the stable pair gas estimate
    ) SummitAdapter(_name, _swapGasEstimate) {
        factory = IGenericFactory(_factory);
        quoter = IQuoter(_quoter);
    }

    function _queryWithCurveId(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut
    ) internal view returns (uint256 amountOut, uint256 curveId) {
        if (_tokenIn == _tokenOut || _amountIn == 0) {
            return (0, 0);
        }

        address[] memory path = new address[](2);
        path[0] = _tokenIn;
        path[1] = _tokenOut;
        uint256[] memory curveIds = new uint256[](1);
        curveIds[0] = 0;

        uint256 constantProductAmtOut;
        // try get quote for constant product pair
        try quoter.getAmountsOut(_amountIn, path, curveIds) returns (uint256[] memory amtsOut) {
            constantProductAmtOut = amtsOut[1];
        } catch {}

        curveIds[0] = 1;
        uint256 stableAmtOut;
        // try get quote for stable pair
        try quoter.getAmountsOut(_amountIn, path, curveIds) returns (uint256[] memory amtsOut) {
            stableAmtOut = amtsOut[1];
        } catch {}

        return stableAmtOut > constantProductAmtOut ? (stableAmtOut, 1) : (constantProductAmtOut, 0);
    }

    function _query(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut
    ) internal view override returns (uint256 amountOut) {
        (amountOut, ) = _queryWithCurveId(_amountIn, _tokenIn, _tokenOut);
    }

    function _swap(
        uint256 _amountIn,
        uint256 _amountOut,
        address _tokenIn,
        address _tokenOut,
        address to
    ) internal override {
        (uint256 amountOut, uint256 curveId) = _queryWithCurveId(_amountIn, _tokenIn, _tokenOut);
        require(amountOut >= _amountOut, "ResAdap: Insufficient amount out");

        address pair = factory.getPair(_tokenIn, _tokenOut, curveId);
        address token0 = IReservoirPair(pair).token0();

        IERC20(_tokenIn).safeTransfer(pair, _amountIn);
        IReservoirPair(pair).swap(
            _tokenIn == token0 ? int256(_amountIn) : -int256(_amountIn),
            true,
            to,
            new bytes(0)
        );
    }
}

//
//            __               ___ ___    __            _  
//           (_  | | |\/| |\/|  |   |    (_ \    / /\  |_) 
//           __) |_| |  | |  | _|_  |    __) \/\/ /--\ |   
//

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;

import "../interface/ISaddle.sol";
import "../interface/IERC20.sol";
import "../interface/IWETH.sol";
import "../lib/SafeERC20.sol";
import "../SummitAdapter.sol";

contract SaddleAdapter is SummitAdapter {
    using SafeERC20 for IERC20;

    mapping(address => bool) public isPoolToken;
    mapping(address => uint8) public tokenIndex;
    address public pool;

    constructor(
        string memory _name,
        address _pool,
        uint256 _swapGasEstimate
    ) SummitAdapter(_name, _swapGasEstimate) {
        pool = _pool;
        _setPoolTokens();
    }

    function _setPoolTokens() internal {
        for (uint8 i = 0; true; i++) {
            try ISaddle(pool).getToken(i) returns (address token) {
                approveToPool(token, UINT_MAX);
                isPoolToken[token] = true;
                tokenIndex[token] = i;
            } catch {
                break;
            }
        }
    }

    function approveToPool(address _tokenIn, uint256 _amount) internal {
        uint256 allowance = IERC20(_tokenIn).allowance(address(this), pool);
        if (allowance < _amount) {
            IERC20(_tokenIn).safeApprove(pool, UINT_MAX);
        }
    }

    function _query(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut
    ) internal view override returns (uint256) {
        if (
            !isPoolToken[_tokenIn] ||
            !isPoolToken[_tokenOut] ||
            _tokenIn == _tokenOut ||
            _amountIn == 0 ||
            ISaddle(pool).paused()
        ) {
            return 0;
        }
        try ISaddle(pool).calculateSwap(tokenIndex[_tokenIn], tokenIndex[_tokenOut], _amountIn) returns (
            uint256 amountOut
        ) {
            return amountOut;
        } catch {
            return 0;
        }
    }

    function _swap(
        uint256 _amountIn,
        uint256 _amountOut,
        address _tokenIn,
        address _tokenOut,
        address _to
    ) internal override {
        // Note that unsupported token will return index 0 which is valid
        ISaddle(pool).swap(tokenIndex[_tokenIn], tokenIndex[_tokenOut], _amountIn, _amountOut, block.timestamp);
        // Confidently transfer amount-out
        _returnTo(_tokenOut, _amountOut, _to);
    }
}

//
//            __               ___ ___    __            _  
//           (_  | | |\/| |\/|  |   |    (_ \    / /\  |_) 
//           __) |_| |  | |  | _|_  |    __) \/\/ /--\ |   
//

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;

import "../interface/ISaddleMeta.sol";
import "../interface/IERC20.sol";
import "../lib/SafeERC20.sol";
import "../SummitAdapter.sol";

contract SaddleMetaAdapter is SummitAdapter {
    using SafeERC20 for IERC20;

    uint256 public constant feeDenominator = 1e10;
    mapping(address => bool) public isPoolToken;
    mapping(address => uint8) public tokenIndex;
    uint256 public poolFeeCompliment;
    address public metaPool;
    address public metaTkn;
    address public pool;

    constructor(
        string memory _name,
        address _pool,
        uint256 _swapGasEstimate
    ) SummitAdapter(_name, _swapGasEstimate) {
        pool = _pool;
        metaPool = ISaddleMeta(pool).metaSwapStorage(); // Pool that holds USDCe, USDTe, DAIe
        _setPoolTokens();
    }

    // Mapping indicator which tokens are included in the pool
    function _setPoolTokens() internal {
        metaTkn = ISaddleMeta(pool).getToken(0);
        approveToPool(metaTkn, UINT_MAX);
        tokenIndex[metaTkn] = 0;
        for (uint8 i = 0; true; i++) {
            try ISaddleMeta(metaPool).getToken(i) returns (address token) {
                approveToPool(token, UINT_MAX);
                isPoolToken[token] = true;
                tokenIndex[token] = i + 1;
            } catch {
                break;
            }
        }
    }

    function approveToPool(address _tokenIn, uint256 _amount) internal {
        uint256 allowance = IERC20(_tokenIn).allowance(address(this), pool);
        if (allowance < _amount) {
            IERC20(_tokenIn).safeApprove(pool, UINT_MAX);
        }
    }

    function _isPaused() internal view returns (bool) {
        return ISaddleMeta(pool).paused() || ISaddleMeta(metaPool).paused();
    }

    function _query(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut
    ) internal view override returns (uint256 amountOut) {
        if (validInput(_amountIn, _tokenIn, _tokenOut) && !_isPaused())
            amountOut = _getAmountOutSafe(_amountIn, _tokenIn, _tokenOut);
    }

    function validInput(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut
    ) internal view returns (bool) {
        return validPath(_tokenIn, _tokenOut) && _amountIn != 0;
    }

    function validPath(address tokenIn, address tokenOut) internal view returns (bool) {
        return (tokenIn == metaTkn && isPoolToken[tokenOut]) || (tokenOut == metaTkn && isPoolToken[tokenIn]);
    }

    function _getAmountOutSafe(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut
    ) internal view returns (uint256 amountOut) {
        try ISaddleMeta(pool).calculateSwapUnderlying(tokenIndex[_tokenIn], tokenIndex[_tokenOut], _amountIn) returns (
            uint256 _amountOut
        ) {
            amountOut = _applyError(_amountOut);
        } catch {}
    }

    function _applyError(uint256 _amount) internal pure returns (uint256) {
        return (_amount * 9998) / 10000;
    }

    function _swap(
        uint256 _amountIn,
        uint256 _amountOut,
        address _tokenIn,
        address _tokenOut,
        address _to
    ) internal override {
        ISaddleMeta(pool).swapUnderlying(
            tokenIndex[_tokenIn],
            tokenIndex[_tokenOut],
            _amountIn,
            _amountOut,
            block.timestamp
        );
        uint256 balThis = IERC20(_tokenOut).balanceOf(address(this));
        _returnTo(_tokenOut, balThis, _to);
    }
}

//
//            __               ___ ___    __            _  
//           (_  | | |\/| |\/|  |   |    (_ \    / /\  |_) 
//           __) |_| |  | |  | _|_  |    __) \/\/ /--\ |   
//

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;

import "../interface/IUniswapFactory.sol";
import "../interface/IUniswapPair.sol";
import "../interface/IERC20.sol";
import "../lib/SafeERC20.sol";
import "../SummitAdapter.sol";

contract UniswapV2Adapter is SummitAdapter {
    using SafeERC20 for IERC20;

    uint256 internal constant FEE_DENOMINATOR = 1e3;
    uint256 public immutable feeCompliment;
    address public immutable factory;

    constructor(
        string memory _name,
        address _factory,
        uint256 _fee,
        uint256 _swapGasEstimate
    ) SummitAdapter(_name, _swapGasEstimate) {
        feeCompliment = FEE_DENOMINATOR - _fee;
        factory = _factory;
    }

    function _getAmountOut(
        uint256 _amountIn,
        uint256 _reserveIn,
        uint256 _reserveOut
    ) internal view returns (uint256 amountOut) {
        // Based on https://github.com/Uniswap/uniswap-v2-periphery/blob/master/contracts/UniswapV2Router02.sol
        uint256 amountInWithFee = _amountIn * feeCompliment;
        uint256 numerator = amountInWithFee * _reserveOut;
        uint256 denominator = _reserveIn * FEE_DENOMINATOR + amountInWithFee;
        amountOut = numerator / denominator;
    }

    function _query(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut
    ) internal view override returns (uint256 amountOut) {
        if (_tokenIn == _tokenOut || _amountIn == 0) {
            return 0;
        }
        address pair = IUniswapFactory(factory).getPair(_tokenIn, _tokenOut);
        if (pair == address(0)) {
            return 0;
        }
        (uint256 r0, uint256 r1, ) = IUniswapPair(pair).getReserves();
        (uint256 reserveIn, uint256 reserveOut) = _tokenIn < _tokenOut ? (r0, r1) : (r1, r0);
        if (reserveIn > 0 && reserveOut > 0) {
            amountOut = _getAmountOut(_amountIn, reserveIn, reserveOut);
        }
    }

    function _swap(
        uint256 _amountIn,
        uint256 _amountOut,
        address _tokenIn,
        address _tokenOut,
        address to
    ) internal override {
        address pair = IUniswapFactory(factory).getPair(_tokenIn, _tokenOut);
        (uint256 amount0Out, uint256 amount1Out) = (_tokenIn < _tokenOut)
            ? (uint256(0), _amountOut)
            : (_amountOut, uint256(0));
        IERC20(_tokenIn).safeTransfer(pair, _amountIn);
        IUniswapPair(pair).swap(amount0Out, amount1Out, to, new bytes(0));
    }
}

//
//            __               ___ ___    __            _  
//           (_  | | |\/| |\/|  |   |    (_ \    / /\  |_) 
//           __) |_| |  | |  | _|_  |    __) \/\/ /--\ |   
//

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;

import "./UniswapV3AdapterBase.sol";

contract UniswapV3Adapter is UniswapV3AdapterBase {

    constructor(
        string memory _name,
        uint256 _swapGasEstimate,
        uint256 _quoterGasLimit,
        address _quoter,
        address _factory,
        uint24[] memory _defaultFees
    ) UniswapV3AdapterBase(_name, _swapGasEstimate, _quoterGasLimit, _quoter, _factory, _defaultFees) {
    }

    function uniswapV3SwapCallback(
        int256 amount0Delta,
        int256 amount1Delta,
        bytes calldata
    ) external {
        if (amount0Delta > 0) {
            IERC20(IUniV3Pool(msg.sender).token0()).transfer(msg.sender, uint256(amount0Delta));
        } else {
            IERC20(IUniV3Pool(msg.sender).token1()).transfer(msg.sender, uint256(amount1Delta));
        }
    }
}

//
//            __               ___ ___    __            _  
//           (_  | | |\/| |\/|  |   |    (_ \    / /\  |_) 
//           __) |_| |  | |  | _|_  |    __) \/\/ /--\ |   
//

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;

import "./UniswapV3likeAdapter.sol";

interface IUniV3Factory {
    function feeAmountTickSpacing(uint24) external view returns (int24);

    function getPool(
        address,
        address,
        uint24
    ) external view returns (address);
}

contract UniswapV3AdapterBase is UniswapV3likeAdapter {
    using SafeERC20 for IERC20;

    address immutable FACTORY;
    mapping(uint24 => bool) public isFeeAmountEnabled;
    uint24[] public feeAmounts;

    constructor(
        string memory _name,
        uint256 _swapGasEstimate,
        uint256 _quoterGasLimit,
        address _quoter,
        address _factory,
        uint24[] memory _defaultFees
    ) UniswapV3likeAdapter(_name, _swapGasEstimate, _quoter, _quoterGasLimit) {
        FACTORY = _factory;
        for (uint i = 0; i < _defaultFees.length; i++) {
            addFeeAmount(_defaultFees[i]);
        }
    }

    function enableFeeAmounts(uint24[] calldata _amounts) external onlyMaintainer {
        for (uint256 i; i < _amounts.length; ++i) enableFeeAmount(_amounts[i]);
    }

    function enableFeeAmount(uint24 _fee) internal {
        require(!isFeeAmountEnabled[_fee], "Fee already enabled");
        if (IUniV3Factory(FACTORY).feeAmountTickSpacing(_fee) == 0)
            revert("Factory doesn't support fee");
        addFeeAmount(_fee);
    }

    function addFeeAmount(uint24 _fee) internal {
        isFeeAmountEnabled[_fee] = true;
        feeAmounts.push(_fee);
    }

    function getBestPool(
        address token0, 
        address token1
    ) internal view override returns (address mostLiquid) {
        uint128 deepestLiquidity;
        for (uint256 i; i < feeAmounts.length; ++i) {
            address pool = IUniV3Factory(FACTORY).getPool(token0, token1, feeAmounts[i]);
            if (pool == address(0))
                continue;
            uint128 liquidity = IUniV3Pool(pool).liquidity();
            if (liquidity > deepestLiquidity) {
                deepestLiquidity = liquidity;
                mostLiquid = pool;
            }
        }
    }
}

//
//            __               ___ ___    __            _  
//           (_  | | |\/| |\/|  |   |    (_ \    / /\  |_) 
//           __) |_| |  | |  | _|_  |    __) \/\/ /--\ |   
//

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;

import "../interface/IERC20.sol";
import "../lib/SafeERC20.sol";
import "../SummitAdapter.sol";

struct QParams {
    address tokenIn;
    address tokenOut;
    int256 amountIn;
    uint24 fee;
}

interface IUniV3Pool {
    function swap(
        address recipient,
        bool zeroForOne,
        int256 amountSpecified,
        uint160 sqrtPriceLimitX96,
        bytes calldata data
    ) external returns (int256 amount0, int256 amount1);

    function token0() external view returns (address);

    function token1() external view returns (address);

    function liquidity() external view returns (uint128);
}

interface IUniV3Quoter {
    function quoteExactInputSingle(
        QParams memory params
    ) external view returns (uint256);

    function quote(
        address,
        bool,
        int256,
        uint160
    ) external view returns (int256, int256);
}

abstract contract UniswapV3likeAdapter is SummitAdapter {
    using SafeERC20 for IERC20;

    uint160 internal constant MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342;
    uint160 internal constant MIN_SQRT_RATIO = 4295128739;
    uint256 public quoterGasLimit;
    address public quoter;

    constructor(
        string memory _name,
        uint256 _swapGasEstimate,
        address _quoter,
        uint256 _quoterGasLimit
    ) SummitAdapter(_name, _swapGasEstimate) {
        setQuoterGasLimit(_quoterGasLimit);
        setQuoter(_quoter);
    }

    function setQuoter(address newQuoter) public onlyMaintainer {
        quoter = newQuoter;
    }

    function setQuoterGasLimit(uint256 newLimit) public onlyMaintainer {
        require(newLimit != 0, "queryGasLimit can't be zero");
        quoterGasLimit = newLimit;
    }

    function getQuoteForPool(
        address pool,
        int256 amountIn,
        address tokenIn,
        address tokenOut
    ) external view returns (uint256) {
        QParams memory params;
        params.amountIn = amountIn;
        params.tokenIn = tokenIn;
        params.tokenOut = tokenOut;
        return getQuoteForPool(pool, params);
    }

    function _query(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut
    ) internal view override returns (uint256 quote) {
        QParams memory params = getQParams(_amountIn, _tokenIn, _tokenOut);
        quote = getQuoteForBestPool(params);
    }

    function _swap(
        uint256 _amountIn,
        uint256 _amountOut,
        address _tokenIn,
        address _tokenOut,
        address _to
    ) internal override {
        QParams memory params = getQParams(_amountIn, _tokenIn, _tokenOut);
        uint256 amountOut = _underlyingSwap(params, new bytes(0));
        require(amountOut >= _amountOut, "Insufficient amountOut");
        _returnTo(_tokenOut, amountOut, _to);
    }

    function getQParams(
        uint256 amountIn,
        address tokenIn,
        address tokenOut
    ) internal pure returns (QParams memory params) {
        params = QParams({ 
            amountIn: int256(amountIn), 
            tokenIn: tokenIn, 
            tokenOut: tokenOut, 
            fee: 0 
        });
    }

    function _underlyingSwap(
        QParams memory params, 
        bytes memory callbackData
    ) internal virtual returns (uint256) {
        address pool = getBestPool(params.tokenIn, params.tokenOut);
        (bool zeroForOne, uint160 priceLimit) = getZeroOneAndSqrtPriceLimitX96(
            params.tokenIn, 
            params.tokenOut
        );
        (int256 amount0, int256 amount1) = IUniV3Pool(pool).swap(
            address(this),
            zeroForOne,
            int256(params.amountIn),
            priceLimit,
            callbackData
        );
        return zeroForOne ? uint256(-amount1) : uint256(-amount0);
    }

    function getQuoteForBestPool(
        QParams memory params
    ) internal view returns (uint256 quote) {
        address bestPool = getBestPool(params.tokenIn, params.tokenOut);
        if (bestPool != address(0)) quote = getQuoteForPool(bestPool, params);
    }

    function getBestPool(
        address token0, 
        address token1
    ) internal view virtual returns (address mostLiquid);
    
    function getQuoteForPool(
        address pool, 
        QParams memory params
    ) internal view returns (uint256) {
        (bool zeroForOne, uint160 priceLimit) = getZeroOneAndSqrtPriceLimitX96(
            params.tokenIn, 
            params.tokenOut
        );
        (int256 amount0, int256 amount1) = getQuoteSafe(
            pool,
            zeroForOne,
            params.amountIn,
            priceLimit
        );
        return zeroForOne ? uint256(-amount1) : uint256(-amount0);
    }

    function getQuoteSafe(
        address pool, 
        bool zeroForOne,
        int256 amountIn,
        uint160 priceLimit
    ) internal view returns (int256 amount0, int256 amount1) {
        bytes memory calldata_ = abi.encodeWithSignature(
            "quote(address,bool,int256,uint160)",
            pool,
            zeroForOne,
            amountIn,
            priceLimit
        );
        (bool success, bytes memory data) = staticCallQuoterRaw(calldata_);
        if (success)
            (amount0, amount1) = abi.decode(data, (int256, int256));
    }

    function staticCallQuoterRaw(
        bytes memory calldata_
    ) internal view returns (bool success, bytes memory data) {
        (success, data) = quoter.staticcall{gas: quoterGasLimit}(calldata_);
    }

    function getZeroOneAndSqrtPriceLimitX96(address tokenIn, address tokenOut)
        internal
        pure
        returns (bool zeroForOne, uint160 sqrtPriceLimitX96)
    {
        zeroForOne = tokenIn < tokenOut;
        sqrtPriceLimitX96 = zeroForOne ? MIN_SQRT_RATIO+1 : MAX_SQRT_RATIO-1;
    }
}

//
//            __               ___ ___    __            _  
//           (_  | | |\/| |\/|  |   |    (_ \    / /\  |_) 
//           __) |_| |  | |  | _|_  |    __) \/\/ /--\ |   
//

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;

import "../interface/IERC20.sol";
import "../lib/SafeERC20.sol";
import "../SummitAdapter.sol";

interface IPairFactory {
    function isPair(address) external view returns (bool);

    function pairCodeHash() external view returns (bytes32);
}

interface IPair {
    function getAmountOut(uint256, address) external view returns (uint256);

    function swap(
        uint256,
        uint256,
        address,
        bytes calldata
    ) external;
}

contract VelodromeAdapter is SummitAdapter {
    using SafeERC20 for IERC20;

    bytes32 immutable PAIR_CODE_HASH;
    address immutable FACTORY;

    constructor(
        string memory _name,
        address _factory,
        uint256 _swapGasEstimate
    ) SummitAdapter(_name, _swapGasEstimate) {
        FACTORY = _factory;
        PAIR_CODE_HASH = getPairCodeHash(_factory);
    }

    function getPairCodeHash(address _factory) internal view returns (bytes32) {
        return IPairFactory(_factory).pairCodeHash();
    }

    function sortTokens(address tokenA, address tokenB) internal pure returns (address token0, address token1) {
        (token0, token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
    }

    // calculates the CREATE2 address for a pair without making any external calls
    function pairFor(
        address tokenA,
        address tokenB,
        bool stable
    ) internal view returns (address pair) {
        (address token0, address token1) = sortTokens(tokenA, tokenB);
        pair = address(
            uint160(
                uint256(
                    keccak256(
                        abi.encodePacked(
                            hex"ff",
                            FACTORY,
                            keccak256(abi.encodePacked(token0, token1, stable)),
                            PAIR_CODE_HASH
                        )
                    )
                )
            )
        );
    }

    function _getAmoutOutSafe(address pair, uint amountIn, address tokenIn) internal view returns (uint) {
        try IPair(pair).getAmountOut(amountIn, tokenIn) returns (uint amountOut) {
            return amountOut;
        } catch {
            return 0;
        }
    }

    function getQuoteAndPair(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut
    ) internal view returns (uint256 amountOut, address pair) {
        address pairStable = pairFor(_tokenIn, _tokenOut, true);
        uint256 amountStable;
        uint256 amountVolatile;
        if (IPairFactory(FACTORY).isPair(pairStable)) {
            amountStable = _getAmoutOutSafe(pairStable, _amountIn, _tokenIn);
        }
        address pairVolatile = pairFor(_tokenIn, _tokenOut, false);
        if (IPairFactory(FACTORY).isPair(pairVolatile)) {
            amountVolatile = _getAmoutOutSafe(pairVolatile, _amountIn, _tokenIn);
        }
        (amountOut, pair) = amountStable > amountVolatile ? (amountStable, pairStable) : (amountVolatile, pairVolatile);
    }

    function _query(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut
    ) internal view override returns (uint256 amountOut) {
        if (_tokenIn != _tokenOut && _amountIn != 0) (amountOut, ) = getQuoteAndPair(_amountIn, _tokenIn, _tokenOut);
    }

    function _swap(
        uint256 _amountIn,
        uint256 _amountOut,
        address _tokenIn,
        address _tokenOut,
        address to
    ) internal override {
        (uint256 amountOut, address pair) = getQuoteAndPair(_amountIn, _tokenIn, _tokenOut);
        require(amountOut >= _amountOut, "Insufficent amount out");
        (uint256 amount0Out, uint256 amount1Out) = (_tokenIn < _tokenOut)
            ? (uint256(0), amountOut)
            : (amountOut, uint256(0));
        IERC20(_tokenIn).safeTransfer(pair, _amountIn);
        IPair(pair).swap(amount0Out, amount1Out, to, new bytes(0));
    }
}

//
//            __               ___ ___    __            _  
//           (_  | | |\/| |\/|  |   |    (_ \    / /\  |_) 
//           __) |_| |  | |  | _|_  |    __) \/\/ /--\ |   
//
//

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;

import "../SummitAdapter.sol";

contract WAvaxAdapter is SummitAdapter {
    address internal constant WAVAX = 0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7;

    constructor(uint256 _swapGasEstimate) SummitAdapter("WAvaxAdapter", _swapGasEstimate) {
        setSwapGasEstimate(_swapGasEstimate);
    }

    function _query(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut
    ) internal pure override returns (uint256 amountOut) {
        if (_tokenIn == WAVAX && _tokenOut == WAVAX) {
            amountOut = _amountIn;
        }
    }

    function _swap(
        uint256,
        uint256 _amountOut,
        address,
        address _tokenOut,
        address _to
    ) internal override {
        _returnTo(_tokenOut, _amountOut, _to);
    }
}

//
//            __               ___ ___    __            _  
//           (_  | | |\/| |\/|  |   |    (_ \    / /\  |_) 
//           __) |_| |  | |  | _|_  |    __) \/\/ /--\ |   
//
//

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;

import "../SummitAdapter.sol";

contract WNativeAdapter is SummitAdapter {
    address internal immutable WNATIVE;

    constructor(
        address _wNative,
        uint256 _swapGasEstimate
    ) SummitAdapter("WAvaxAdapter", _swapGasEstimate) {
        WNATIVE = _wNative;
        setSwapGasEstimate(_swapGasEstimate);
    }

    function _query(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut
    ) internal view override returns (uint256 amountOut) {
        if (_tokenIn == WNATIVE && _tokenOut == WNATIVE)
            amountOut = _amountIn;
    }

    function _swap(
        uint256 _amountIn,
        uint256,
        address,
        address _tokenOut,
        address _to
    ) internal override {
        _returnTo(_tokenOut, _amountIn, _to);
    }
}

//
//            __               ___ ___    __            _  
//           (_  | | |\/| |\/|  |   |    (_ \    / /\  |_) 
//           __) |_| |  | |  | _|_  |    __) \/\/ /--\ |   
//
//                            ,=.
//                ,=""""==.__.="  o".___
//          ,=.=="                  ___/
//    ,==.,"    ,          , \,===""
//   <     ,==)  \"'"=._.==)  \
//    `==''    `"           `"
//

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;

import "../interface/IWombat.sol";
import "../interface/IERC20.sol";
import "../interface/IWETH.sol";
import "../lib/SafeERC20.sol";
import "../SummitAdapter.sol";

contract WombatAdapter is SummitAdapter {
    using SafeERC20 for IERC20;

    event PartialPoolSupport(address pool, address[] tkns);
    event AddPoolSupport(address pool);
    event RmPoolSupport(address pool);

    mapping(address => mapping(address => address)) private tknToTknToPool;

    constructor(
        string memory _name,
        uint256 _swapGasEstimate,
        address[] memory _initPools
    ) SummitAdapter(_name, _swapGasEstimate) {
        addPools(_initPools);
    }

    function getPoolForTkns(address tknIn, address tknOut) public view returns (address) {
        return tknToTknToPool[tknIn][tknOut];
    }

    function _approveIfNeeded(address tkn, address spender) internal {
        uint256 allowance = IERC20(tkn).allowance(address(this), spender);
        if (allowance < UINT_MAX) {
            IERC20(tkn).approve(spender, UINT_MAX);
        }
    }

    // @dev Returns false if repeated tkns
    function _poolSupportsTkns(address pool, address[] memory tkns) internal view returns (bool) {
        address[] memory supportedTkns = IWombat(pool).getTokens();
        uint256 supportedCount;
        for (uint256 i = 0; i < supportedTkns.length; i++) {
            for (uint256 j = 0; j < tkns.length; j++) {
                if (supportedTkns[i] == tkns[j]) {
                    supportedCount++;
                    break;
                }
            }
        }
        return supportedCount == tkns.length;
    }

    function _setPoolForTkns(address[] memory tkns, address pool) internal {
        for (uint256 i = 0; i < tkns.length; i++) {
            for (uint256 j = 0; j < tkns.length; j++) {
                if (i != j) {
                    tknToTknToPool[tkns[i]][tkns[j]] = pool;
                    if (pool != address(0)) {
                        _approveIfNeeded(tkns[i], pool);
                    }
                }
            }
        }
    }

    function addPools(address[] memory pools) public onlyMaintainer {
        for (uint256 i = 0; i < pools.length; i++) {
            address pool = pools[i];
            address[] memory supportedTkns = IWombat(pool).getTokens();
            _setPoolForTkns(supportedTkns, pool);
            emit AddPoolSupport(pool);
        }
    }

    function setPoolForTkns(address pool, address[] memory tkns) external onlyMaintainer {
        require(tkns.length > 1, "At least two tkns");
        require(pool != address(0), "Only non-zero pool");
        require(_poolSupportsTkns(pool, tkns), "Pool does not support tkns");
        // Assume above checks there is no repeats
        _setPoolForTkns(tkns, pool);
        emit PartialPoolSupport(pool, tkns);
    }

    function rmPools(address[] calldata pools) external onlyMaintainer {
        for (uint256 i = 0; i < pools.length; i++) {
            address pool = pools[i];
            address[] memory supportedTkns = IWombat(pool).getTokens();
            _setPoolForTkns(supportedTkns, address(0));
            emit RmPoolSupport(pool);
        }
    }

    function _query(uint256 _amountIn, address _tokenIn, address _tokenOut) internal view override returns (uint256) {
        address pool = getPoolForTkns(_tokenIn, _tokenOut);
        if (pool == address(0) || _amountIn == 0 || IWombat(pool).paused()) {
            return 0;
        }
        try IWombat(pool).quotePotentialSwap(_tokenIn, _tokenOut, int256(_amountIn)) returns (uint256 amountOut) {
            return amountOut;
        } catch {
            return 0;
        }
    }

    function _swap(
        uint256 _amountIn,
        uint256 _amountOut,
        address _tokenIn,
        address _tokenOut,
        address _to
    ) internal override {
        address pool = getPoolForTkns(_tokenIn, _tokenOut);
        IWombat(pool).swap(_tokenIn, _tokenOut, _amountIn, _amountOut, _to, block.timestamp);
    }
}

//
//            __               ___ ___    __            _  
//           (_  | | |\/| |\/|  |   |    (_ \    / /\  |_) 
//           __) |_| |  | |  | _|_  |    __) \/\/ /--\ |   
//

// SPDX-License-Identifier: GPL-3.0-only

pragma solidity ^0.8.0;

import "../interface/IWooPP.sol";
import "../interface/IERC20.sol";
import "../lib/SafeERC20.sol";
import "../SummitAdapter.sol";

contract WoofiAdapter is SummitAdapter {
    using SafeERC20 for IERC20;

    address public immutable quoteToken;
    address public immutable pool;
    address public rebateCollector;

    constructor(
        string memory _name,
        uint256 _swapGasEstimate,
        address _pool
    ) SummitAdapter(_name, _swapGasEstimate) {
        address _quoteToken = IWooPP(_pool).quoteToken();
        IERC20(_quoteToken).approve(_pool, UINT_MAX);
        quoteToken = _quoteToken;
        pool = _pool;
    }

    function setRebateCollector(address _rebateCollector) external onlyMaintainer {
        rebateCollector = _rebateCollector;
    }

    function _query(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut
    ) internal view override returns (uint256 amountOut) {
        if (_amountIn == 0) {
            return 0;
        }
        if (_tokenIn == quoteToken) {
            amountOut = _safeQuery(IWooPP(pool).querySellQuote, _tokenOut, _amountIn);
        } else if (_tokenOut == quoteToken) {
            amountOut = _safeQuery(IWooPP(pool).querySellBase, _tokenIn, _amountIn);
        } else {
            uint256 quoteAmount = _safeQuery(IWooPP(pool).querySellBase, _tokenIn, _amountIn);
            amountOut = _safeQuery(IWooPP(pool).querySellQuote, _tokenOut, quoteAmount);
        }
    }

    function _safeQuery(
        function(address, uint256) external view returns (uint256) qFn,
        address _baseToken,
        uint256 _baseAmount
    ) internal view returns (uint256) {
        try qFn(_baseToken, _baseAmount) returns (uint256 amountOut) {
            return amountOut;
        } catch {
            return 0;
        }
    }

    function _swap(
        uint256 _amountIn,
        uint256 _amountOut,
        address _tokenIn,
        address _tokenOut,
        address _to
    ) internal override {
        _approveIfNeeded(_tokenIn, _amountIn);
        uint256 realToAmount;
        if (_tokenIn == quoteToken) {
            realToAmount = IWooPP(pool).sellQuote(_tokenOut, _amountIn, _amountOut, _to, rebateCollector);
        } else if (_tokenOut == quoteToken) {
            realToAmount = IWooPP(pool).sellBase(_tokenIn, _amountIn, _amountOut, _to, rebateCollector);
        } else {
            uint256 quoteAmount = IWooPP(pool).sellBase(_tokenIn, _amountIn, 0, address(this), rebateCollector);
            realToAmount = IWooPP(pool).sellQuote(_tokenOut, quoteAmount, _amountOut, _to, rebateCollector);
        }
    }

    function _approveIfNeeded(address _tokenIn, uint256 _amount) internal {
        uint256 allowance = IERC20(_tokenIn).allowance(address(this), pool);
        if (allowance < _amount) {
            IERC20(_tokenIn).approve(pool, UINT_MAX);
        }
    }
}

//
//            __               ___ ___    __            _  
//           (_  | | |\/| |\/|  |   |    (_ \    / /\  |_) 
//           __) |_| |  | |  | _|_  |    __) \/\/ /--\ |   
//

// SPDX-License-Identifier: GPL-3.0-only

pragma solidity ^0.8.0;

import "../interface/IWooPPV2.sol";
import "../interface/IERC20.sol";
import "../lib/SafeERC20.sol";
import "../SummitAdapter.sol";

contract WoofiV2Adapter is SummitAdapter {
    using SafeERC20 for IERC20;

    address public immutable pool;
    address public rebateCollector;

    constructor(
        string memory _name,
        uint256 _swapGasEstimate,
        address _pool
    ) SummitAdapter(_name, _swapGasEstimate) {
        pool = _pool;
    }

    function setRebateCollector(address _rebateCollector) external onlyMaintainer {
        rebateCollector = _rebateCollector;
    }

    function _query(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut
    ) internal view override returns (uint256) {
        if (_amountIn == 0) {
            return 0;
        }

        try IWooPPV2(pool).query(_tokenIn, _tokenOut, _amountIn) returns (uint256 amountOut) {
            return amountOut;
        } catch {
            return 0;
        }
    }

    function _swap(
        uint256 _amountIn,
        uint256 _amountOut,
        address _tokenIn,
        address _tokenOut,
        address _to
    ) internal override {
        IERC20(_tokenIn).safeTransfer(pool, _amountIn);
        IWooPPV2(pool).swap(_tokenIn, _tokenOut, _amountIn, _amountOut, _to, rebateCollector);
    }
}

//
//            __               ___ ___    __            _  
//           (_  | | |\/| |\/|  |   |    (_ \    / /\  |_) 
//           __) |_| |  | |  | _|_  |    __) \/\/ /--\ |   
//
//

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;

import "../interface/IxJOE.sol";
import "../lib/SafeERC20.sol";

import "../SummitAdapter.sol";

contract XJoeAdapter is SummitAdapter {
    using SafeERC20 for IERC20;

    address public constant JOE = 0x6e84a6216eA6dACC71eE8E6b0a5B7322EEbC0fDd;
    address public constant XJOE = 0x57319d41F71E81F3c65F2a47CA4e001EbAFd4F33;

    constructor(uint256 _swapGasEstimate) SummitAdapter("XJoeAdapter", _swapGasEstimate) {
        setAllowances();
    }

    function setAllowances() internal {
        IERC20(XJOE).safeApprove(XJOE, UINT_MAX);
        IERC20(JOE).safeApprove(XJOE, UINT_MAX);
    }

    function queryEnter(uint256 _amountIn) internal view returns (uint256) {
        uint256 totalJoe = IERC20(JOE).balanceOf(XJOE);
        uint256 totalShares = IxJOE(XJOE).totalSupply();
        if (totalShares == 0 || totalJoe == 0) {
            return _amountIn;
        }
        return (_amountIn * totalShares) / totalJoe;
    }

    function queryLeave(uint256 _amountIn) internal view returns (uint256) {
        uint256 totalShares = IxJOE(XJOE).totalSupply();
        return (_amountIn * IERC20(JOE).balanceOf(XJOE)) / totalShares;
    }

    function _query(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut
    ) internal view override returns (uint256 amountOut) {
        if (_tokenIn == JOE && _tokenOut == XJOE) {
            return queryEnter(_amountIn);
        } else if (_tokenIn == XJOE && _tokenOut == JOE) {
            return queryLeave(_amountIn);
        }
    }

    function _swap(
        uint256 _amountIn,
        uint256 _amountOut,
        address _tokenIn,
        address _tokenOut,
        address _to
    ) internal override {
        if (_tokenIn == JOE && _tokenOut == XJOE) {
            IxJOE(XJOE).enter(_amountIn);
        } else if (_tokenIn == XJOE && _tokenOut == JOE) {
            IxJOE(XJOE).leave(_amountIn);
        } else {
            revert("XJoeAdapter: Unsupported token");
        }
        // Confidently transfer amount-out
        _returnTo(_tokenOut, _amountOut, _to);
    }
}

//
//            __               ___ ___    __            _  
//           (_  | | |\/| |\/|  |   |    (_ \    / /\  |_) 
//           __) |_| |  | |  | _|_  |    __) \/\/ /--\ |   
//
//

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;

import "../interface/IYYDerivative.sol";
import "../lib/SafeERC20.sol";

import "../SummitAdapter.sol";

contract YYDerivativeAdapter is SummitAdapter {
    using SafeERC20 for IERC20;

    address public immutable derivative;
    address public immutable underlying;

    constructor(
        string memory _name,
        uint256 _swapGasEstimate,
        address _derivative,
        address _underlying
    ) SummitAdapter(_name, _swapGasEstimate) {
        derivative = _derivative;
        underlying = _underlying;
    }

    function _query(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut
    ) internal view override returns (uint256 amountOut) {
        if (_tokenIn == underlying && _tokenOut == derivative && IYYDerivative(derivative).depositsEnabled()) {
            return _amountIn;
        }
        return 0;
    }

    function _swap(
        uint256 _amountIn,
        uint256, /*_amountOut*/
        address _tokenIn,
        address _tokenOut,
        address _to
    ) internal override {
        if (_tokenIn == underlying && _tokenOut == derivative) {
            IERC20(underlying).approve(derivative, _amountIn);
            IYYDerivative(derivative).deposit(_amountIn);
        } else {
            revert("YYDerivativeAdapter: Unsupported token");
        }
        // Confidently transfer amount-out
        _returnTo(_tokenOut, _amountIn, _to);
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IAdapter {
    function name() external view returns (string memory);

    function swapGasEstimate() external view returns (uint256);

    function swap(
        uint256,
        uint256,
        address,
        address,
        address
    ) external;

    function query(
        uint256,
        address,
        address
    ) external view returns (uint256);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./IPoolSwapStructs.sol";

interface IBasePool is IPoolSwapStructs {
    function getPoolId() external view returns (bytes32);
}

// SPDX-License-Identifier: MIT
pragma solidity >=0.5.16;

interface IBlast {
    enum GasMode {
        VOID,
        CLAIMABLE
    }

    function configureClaimableGas() external;
    function configureGovernor(address governor) external;

    function claimAllGas(
        address contractAddress,
        address recipientOfGas
    ) external returns (uint256);

    function claimGasAtMinClaimRate(
        address contractAddress,
        address recipientOfGas,
        uint256 minClaimRateBips
    ) external returns (uint256);

    function claimMaxGas(
        address contractAddress,
        address recipientOfGas
    ) external returns (uint256);

    function claimGas(
        address contractAddress,
        address recipientOfGas,
        uint256 gasToClaim,
        uint256 gasSecondsToConsume
    ) external returns (uint256);

    function readGasParams(
        address contractAddress
    )
        external
        view
        returns (
            uint256 etherSeconds,
            uint256 etherBalance,
            uint256 lastUpdated,
            GasMode
        );
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface ICurve1 {
    function underlying_coins(uint256 index) external view returns (address);

    function exchange_underlying(
        uint256 tokenIndexFrom,
        uint256 tokenIndexTo,
        uint256 dx,
        uint256 minDy
    ) external;

    function get_dy_underlying(
        uint256 tokenIndexFrom,
        uint256 tokenIndexTo,
        uint256 dx
    ) external view returns (uint256);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface ICurve2 {
    function underlying_coins(uint256 index) external view returns (address);

    function get_dy_underlying(
        int128 tokenIndexFrom,
        int128 tokenIndexTo,
        uint256 dx
    ) external view returns (uint256);

    function exchange_underlying(
        int128 tokenIndexFrom,
        int128 tokenIndexTo,
        uint256 dx,
        uint256 minDy
    ) external;
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface ICurveMeta {
    function base_coins(uint256) external view returns (address);

    function coins(uint256 index) external view returns (address);

    function get_dy_underlying(
        int128 tokenIndexFrom,
        int128 tokenIndexTo,
        uint256 dx
    ) external view returns (uint256);

    function exchange_underlying(
        int128 tokenIndexFrom,
        int128 tokenIndexTo,
        uint256 dx,
        uint256 minDy
    ) external;
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface ICurvePlain128 {
    function coins(uint256 index) external view returns (address);

    function exchange(
        int128 tokenIndexFrom,
        int128 tokenIndexTo,
        uint256 dx,
        uint256 minDy
    ) external returns (uint256);

    function get_dy(
        int128 tokenIndexFrom,
        int128 tokenIndexTo,
        uint256 dx
    ) external view returns (uint256);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface ICurvePlain128Native {
    function coins(uint256 index) external view returns (address);

    function exchange(
        int128 tokenIndexFrom,
        int128 tokenIndexTo,
        uint256 dx,
        uint256 minDy
    ) external payable returns (uint256);

    function get_dy(
        int128 tokenIndexFrom,
        int128 tokenIndexTo,
        uint256 dx
    ) external view returns (uint256);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface ICurvePlain256 {
    function coins(uint256 index) external view returns (address);

    function exchange(
        uint256 tokenIndexFrom,
        uint256 tokenIndexTo,
        uint256 dx,
        uint256 minDy
    ) external;

    function get_dy(
        uint256 tokenIndexFrom,
        uint256 tokenIndexTo,
        uint256 dx
    ) external view returns (uint256);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IDodoHelper {
    function querySellQuoteToken(address dodo, uint256 amount) external view returns (uint256);
}

interface IDodoV1 {
    function _QUOTE_TOKEN_() external view returns (address);

    function _BASE_TOKEN_() external view returns (address);

    function querySellBaseToken(uint256 amount) external view returns (uint256);

    function queryBuyBaseToken(uint256 amount) external view returns (uint256);

    function sellBaseToken(
        uint256 amount,
        uint256 minReceiveQuote,
        bytes calldata data
    ) external returns (uint256);

    function buyBaseToken(
        uint256 amount,
        uint256 maxPayQuote,
        bytes calldata data
    ) external returns (uint256);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IDodoV2 {
    function _QUOTE_TOKEN_() external view returns (address);

    function _BASE_TOKEN_() external view returns (address);

    function querySellBase(address trader, uint256 payBaseAmount) external view returns (uint256 receiveQuoteAmount);

    function querySellQuote(address trader, uint256 payQuoteAmount) external view returns (uint256 receiveBaseAmount);

    function sellBase(address to) external returns (uint256 receiveQuoteAmount);

    function sellQuote(address to) external returns (uint256 receiveBaseAmount);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IERC20 {
    event Approval(address, address, uint256);
    event Transfer(address, address, uint256);

    function name() external view returns (string memory);
    function symbol() external view returns (string memory);

    function decimals() external view returns (uint8);

    function transferFrom(
        address,
        address,
        uint256
    ) external returns (bool);

    function allowance(address, address) external view returns (uint256);

    function approve(address, uint256) external returns (bool);

    function transfer(address, uint256) external returns (bool);

    function balanceOf(address) external view returns (uint256);

    function nonces(address) external view returns (uint256); // Only tokens that support permit

    function permit(
        address,
        address,
        uint256,
        uint256,
        uint8,
        bytes32,
        bytes32
    ) external; // Only tokens that support permit

    function swap(address, uint256) external; // Only Avalanche bridge tokens

    function swapSupply(address) external view returns (uint256); // Only Avalanche bridge tokens

    function totalSupply() external view returns (uint256);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

// @note: operator stands for interface address
interface IgAVAX {
    function setApprovalForAll(address operator, bool approved) external;

    function pricePerShare(uint256 _id) external view returns (uint256);

    function balanceOf(address account, uint256 id) external view returns (uint256);

    function isInterface(address operator, uint256 id) external view returns (bool);

    function isApprovedForAll(address account, address operator) external view returns (bool);
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

interface IGenericFactory {
    function allPairs() external view returns (address[] memory);
    function getPair(address tokenA, address tokenB, uint256 curveId) external view returns (address);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IGeodePortal {
    function gAVAX() external view returns (address);

    function getNameFromId(uint256 _id) external view returns (bytes memory);

    function planetCurrentInterface(uint256 _id) external view returns (address);

    function planetWithdrawalPool(uint256 _id) external view returns (address);

    function getMaintainerFromId(uint256) external view returns (address);

    function isStakingPausedForPool(uint256) external view returns (bool);

    function unpauseStakingForPool(uint256) external;

    function pauseStakingForPool(uint256) external;

    function stake(
        uint256 planetId,
        uint256 minGavax,
        uint256 deadline
    ) external payable returns (uint256 totalgAvax);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IGeodeWP {
    function paused() external view returns (bool);

    function getDebt() external view returns (uint256);

    function getToken() external view returns (uint256);

    function getERC1155() external view returns (address);

    function getTokenBalance(uint8) external view returns (uint256);

    function calculateSwap(
        uint8 tokenIndexFrom,
        uint8 tokenIndexTo,
        uint256 dx
    ) external view returns (uint256);

    function swap(
        uint8 tokenIndexFrom,
        uint8 tokenIndexTo,
        uint256 dx,
        uint256 minDy,
        uint256 deadline
    ) external payable returns (uint256);

    function addLiquidity(
        uint256[] calldata amounts,
        uint256 minToMint,
        uint256 deadline
    ) external payable;
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IGlpManager {
    function getAumInUsdg(bool maximise) external view returns (uint256);

    function vault() external view returns (address);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IGmxRewardRouter {
    function glpManager() external view returns (address);

    function mintAndStakeGlp(
        address _token,
        uint256 _amount,
        uint256 _minUsdg,
        uint256 _minGlp
    ) external returns (uint256);

    function unstakeAndRedeemGlp(
        address _tokenOut,
        uint256 _glpAmount,
        uint256 _minOut,
        address _receiver
    ) external returns (uint256);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IGmxVaultPriceFeed {
    function getPrice(address, bool, bool, bool) external view returns (uint256);
}

interface IGmxVaultUtils {
    function getSwapFeeBasisPoints(address, address, uint256) external view returns (uint256);

    function getBuyUsdgFeeBasisPoints(address _token, uint256 _usdgAmount) external view returns (uint256);

    function getSellUsdgFeeBasisPoints(address _token, uint256 _usdgAmount) external view returns (uint256);
}

interface IGmxVault {
    function swap(address, address, address) external;

    function whitelistedTokens(address) external view returns (bool);

    function isSwapEnabled() external view returns (bool);

    function vaultUtils() external view returns (IGmxVaultUtils);

    function priceFeed() external view returns (IGmxVaultPriceFeed);

    function allWhitelistedTokensLength() external view returns (uint256);

    function allWhitelistedTokens(uint256) external view returns (address);

    function maxUsdgAmounts(address) external view returns (uint256);

    function usdgAmounts(address) external view returns (uint256);

    function reservedAmounts(address) external view returns (uint256);

    function bufferAmounts(address) external view returns (uint256);

    function poolAmounts(address) external view returns (uint256);

    function usdg() external view returns (address);

    function hasDynamicFees() external view returns (bool);

    function stableTokens(address) external view returns (bool);

    function getFeeBasisPoints(address, uint256, uint256, uint256, bool) external view returns (uint256);

    function stableSwapFeeBasisPoints() external view returns (uint256);

    function swapFeeBasisPoints() external view returns (uint256);

    function stableTaxBasisPoints() external view returns (uint256);

    function taxBasisPoints() external view returns (uint256);

    function mintBurnFeeBasisPoints() external view returns (uint256);

    function setBufferAmount(address, uint256) external;

    function gov() external view returns (address);

    function getMaxPrice(address _token) external view returns (uint256);

    function getMinPrice(address _token) external view returns (uint256);

    function adjustForDecimals(uint256 _amount, address _tokenDiv, address _tokenMul) external view returns (uint256);

    function getRedemptionAmount(address _token, uint256 _usdgAmount) external view returns (uint256);

    function getTargetUsdgAmount(address _token) external view returns (uint256);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IKyberPool {
    function swap(
        uint256 amount0Out,
        uint256 amount1Out,
        address to,
        bytes calldata data
    ) external;

    function getTradeInfo()
        external
        view
        returns (
            uint112 _vReserve0,
            uint112 _vReserve1,
            uint112 reserve0,
            uint112 reserve1,
            uint256 feeInPrecision
        );

    function token0() external view returns (address);

    function token1() external view returns (address);
}

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

interface ILBPair {
    function getTokenX() external view returns (address);

    function getTokenY() external view returns (address);

    function swap(bool swapForY, address to) external returns (bytes32 amountsOut);

    function getSwapOut(uint128 amountIn, bool swapForY)
        external
        view
        returns (uint128 amountInLeft, uint128 amountOut, uint128 fee);
}

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

interface ILBFactory {
    struct LBPairInformation {
        uint24 binStep;
        address LBPair;
        bool createdByOwner;
        bool ignoredForRouting;
    }

    function getAllLBPairs(address tokenX, address tokenY)
        external
        view
        returns (LBPairInformation[] memory LBPairsBinStep);
}

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

interface ILBPair {
    function tokenX() external view returns (address);

    function tokenY() external view returns (address);

    function swap(bool sentTokenY, address to) external returns (uint256 amountXOut, uint256 amountYOut);
}

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

interface ILBRouter {
    function factory() external view returns (address);

    function getSwapOut(
        address pair,
        uint256 amountIn,
        bool swapForY
    ) external view returns (uint256 amountOut, uint256 feesIn);

    function swapExactTokensForTokens(
        uint256 amountIn,
        uint256 amountOutMin,
        uint256[] memory pairBinSteps,
        address[] memory tokenPath,
        address to,
        uint256 deadline
    ) external returns (uint256 amountOut);
}

File 80 of 115 : IMinimalSwapInfoPool.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./IBasePool.sol";

interface IMinimalSwapInfoPool is IBasePool {
    function onSwap(
        SwapRequest memory swapRequest,
        uint256 currentBalanceTokenIn,
        uint256 currentBalanceTokenOut
    ) external view returns (uint256 amount);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./IERC20.sol";

interface ImSUMMIT is IERC20 {
    function unmoon(uint256, address) external;

    function moon(uint256, address) external;
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IPlatypus {
    // Views
    function quotePotentialSwap(
        address fromToken,
        address totoken,
        uint256 fromAmount
    ) external view returns (uint256 potentialOutcome); // Second arg (haircut) is not used

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

    function paused() external view returns (bool);

    // Modifiers
    function swap(
        address fromToken,
        address toToken,
        uint256 fromAmount,
        uint256 minAmountOut,
        address to,
        uint256 deadline
    ) external;

    function pause() external;

    function unpause() external;
}

File 83 of 115 : IPoolSwapStructs.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./IVault.sol";

interface IPoolSwapStructs {
    struct SwapRequest {
        IVault.SwapKind kind;
        IERC20 tokenIn;
        IERC20 tokenOut;
        uint256 amount;
        bytes32 poolId;
        uint256 lastChangeBlock;
        address from;
        address to;
        bytes userData;
    }
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

interface IReservoirPair {
    function token0() external returns (address);
    function token1() external returns (address);
    function swap(int256 aAmount, bool aExactIn, address aTo, bytes calldata aData)
        external
        returns (uint256 rAmountOut);
}

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

interface IQuoter {
    /// @dev aPath array of ERC20 tokens to swap into
    function getAmountsOut(uint256 aAmountIn, address[] calldata aPath, uint256[] calldata aCurveIds)
        external
        view
        returns (uint256[] memory rAmountsOut);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./IERC20.sol";

interface ISaddle {
    function getTokenIndex(address tokenAddress) external view returns (uint8);

    function getTokenBalance(uint8 index) external view returns (uint256);

    function getToken(uint8 index) external view returns (address);

    function getVirtualPrice() external view returns (uint256);

    function owner() external view returns (address);

    function paused() external view returns (bool);

    function calculateSwap(
        uint8 tokenIndexFrom,
        uint8 tokenIndexTo,
        uint256 dx
    ) external view returns (uint256);

    function unpause() external;

    function pause() external;

    function swap(
        uint8 tokenIndexFrom,
        uint8 tokenIndexTo,
        uint256 dx,
        uint256 minDy,
        uint256 deadline
    ) external returns (uint256);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./IERC20.sol";

struct SwapStorage {
    uint256 initialA;
    uint256 futureA;
    uint256 initialATime;
    uint256 futureATime;
    uint256 swapFee;
    uint256 adminFee;
    address lpToken;
}

interface ISaddleMeta {
    function getToken(uint8 index) external view returns (address);

    function getVirtualPrice() external view returns (uint256);

    function owner() external view returns (address);

    function paused() external view returns (bool);

    function calculateSwapUnderlying(
        uint8 tokenIndexFrom,
        uint8 tokenIndexTo,
        uint256 dx
    ) external view returns (uint256);

    function unpause() external;

    function pause() external;

    function swapUnderlying(
        uint8 tokenIndexFrom,
        uint8 tokenIndexTo,
        uint256 dx,
        uint256 minDy,
        uint256 deadline
    ) external returns (uint256);

    function metaSwapStorage() external returns (address);

    function swapStorage() external returns (SwapStorage memory);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./IERC20.sol";

interface ISAVAX is IERC20 {
    function getSharesByPooledAvax(uint256) external view returns (uint256);

    function submit() external payable returns (uint256);

    function mintingPaused() external view returns (bool);

    function totalPooledAvax() external view returns (uint256);

    function totalPooledAvaxCap() external view returns (uint256);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IStabilityFund {
    function swap(
        address token0,
        uint256 amount,
        address token1
    ) external;

    function swapEnabled() external view returns (bool);

    function isStableToken(address) external view returns (bool);

    function isTokenDisabled(address) external view returns (bool);

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

    function getStableTokensCount() external view returns (uint256);

    function swapFee() external view returns (uint256);
}

//
//            __               ___ ___    __            _  
//           (_  | | |\/| |\/|  |   |    (_ \    / /\  |_) 
//           __) |_| |  | |  | _|_  |    __) \/\/ /--\ |   
//

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;

interface ISummitPoints {
  event UpdatedVolumeAdapter(address indexed _volumeAdapter);
  event UpdatedReferralsContract(address indexed _referrals);
  event UpdatedGlobalBoost(uint256 _boost);
  event UpdatedVolumeScalers(uint256 _refVolumeScaler, uint256 _adapterVolumeScaler);
  event UpdatedBlacklisted(address _add,bool _blacklisted);
  event AddedUserVolume(address indexed _user, uint256 _volume);
  event AddedReferrerVolume(address indexed _referrer, address indexed _user, uint256 _volume);
  event AddedAdapterVolume(address indexed _adapter, uint256 _volume);
  event TransferredVolume(address indexed _caller, address indexed _from, address indexed _to, uint256 _selfVolume, uint256 _refVolume, uint256 _adapterVolume);
  event UpdatedDelegate(address indexed _caller, address indexed _user, address indexed _delegate);
  event UpdatedAdapterDelegate(address indexed _adapter, address indexed _delegate);
  event SummitTeamGivenPoints(address indexed _user, uint256 _volume);

  function setVolumeAdapter(address _volumeAdapter) external;
  function setReferralsContract(address _referrals) external;
  function setGlobalBoost(uint256 _boost) external;
  function setBlacklisted(address _add, bool _blacklisted) external;
  function summitTeamGivePoints(address _add, uint256 _volume) external;
  function setVolumeScalers(uint256 _refVolumeScaler, uint256 _adapterVolumeScaler) external;
  function setDelegate(address _user, address _delegate) external;
  function setAdapterDelegate(address _adapter, address _delegate) external;
  function addVolume(address _add, uint256 _volume) external;
  function addAdapterVolume(address _adapter, uint256 _volume) external;
  function transferVolume(address _from, address _to, uint256 _selfVolume, uint256 _refVolume, uint256 _adapterVolume) external;
  function getVolume(address _add) external view returns (uint256 selfVolume, uint256 refVolume, uint256 adapterVolume);
  function getPoints(address _add) external view returns (uint256 pointsFromSelf, uint256 pointsFromRef, uint256 pointsFromAdapter, uint256 pointsTotal);
}

//
//            __               ___ ___    __            _  
//           (_  | | |\/| |\/|  |   |    (_ \    / /\  |_) 
//           __) |_| |  | |  | _|_  |    __) \/\/ /--\ |   
//

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface ISummitReferrals {

  event UpdatedPointsContract(address indexed _pointsContract);
  event BoostedReferrer(address indexed referrer, uint8 _boostLevel);
  event BoostedReferrers(address[] referrer, uint8[] _boostLevel);
  event UpdatedReferrer(address indexed _add, address indexed _referrer);
  event UpdatedLevelData(uint256[] _refPointsReq, uint256[] _selfPointsReq, uint256[] _refsReq, uint256[] _multRew, uint256 _hasReferrerBonusMult);
  event UpdatedReferralCode(address indexed _caller, address indexed _user, string _code);

  function setPointsContract(address _pointsContract) external;
  function setReferrer(address _referrer, string memory _code) external;
  function setReferralCode(string memory _code) external;
  function maintainerSetReferralCode(address _add, string memory _code) external;
  function boostReferrer(address _referrer, uint8 _boostLevel) external;
  function boostReferrers(address[] memory _referrers, uint8[] memory _boostLevels) external;
  function setLevelData(uint256[] memory _refPointsReq, uint256[] memory _selfPointsReq, uint256[] memory _refsReq, uint256[] memory _multRew, uint256 _hasReferrerBonusMult) external;
  function getReferrer(address _add) external view returns (address);
  function getReferrerCode(address _add) external view returns (string memory);
  function getRefsCount(address _add) external view returns (uint256);
  function getReferrerLevelWithoutBoost(address _add) external view returns (uint8);
  function getReferrerLevel(address _add) external view returns (uint8);
  function getLevelRequirements(uint8 _level) external view returns (uint256 selfVolume, uint256 refVolume, uint256 refsCount);
  function getUserNextLevelRequirements(address _add) external view returns (uint256 selfVolume, uint256 refVolume, uint256 refsCount);
  function getRefVolumeBonusMultiplier(address _add) external view returns (uint256);
  function getSelfVolumeMultiplier(address _add) external view returns (uint256);
}

//
//            __               ___ ___    __            _  
//           (_  | | |\/| |\/|  |   |    (_ \    / /\  |_) 
//           __) |_| |  | |  | _|_  |    __) \/\/ /--\ |   
//

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;


struct Query {
    address adapter;
    address tokenIn;
    address tokenOut;
    uint256 amountOut;
}
struct Offer {
    bytes amounts;
    bytes adapters;
    bytes path;
    uint256 gasEstimate;
}
struct FormattedOffer {
    uint256[] amounts;
    address[] adapters;
    address[] path;
    uint256 gasEstimate;
}
struct Trade {
    uint256 amountIn;
    uint256 amountOut;
    address[] path;
    address[] adapters;
}

interface ISummitRouter {

    event UpdatedTrustedTokens(address[] _newTrustedTokens);
    event UpdatedTokenVolumeMultipliers(address[] _tokens, uint256[] _pointMultipliers);
    event UpdatedTokenBonusMultipliers(address [] _tokens, uint256[] _multipliers);
    event UpdatedAdapters(address[] _newAdapters);
    event UpdatedVolumeAdapter(address _volumeAdapter);
    event UpdatedMinFee(uint256 _oldMinFee, uint256 _newMinFee);
    event UpdatedFeeClaimer(address _oldFeeClaimer, address _newFeeClaimer);
    event SummitSwap(address indexed _tokenIn, address indexed _tokenOut, uint256 _amountIn, uint256 _amountOut);

    // admin
    function setTrustedTokens(address[] memory _trustedTokens) external;
    function setAdapters(address[] memory _adapters) external;
    function setVolumeAdapter(address _volumeAdapter) external;
    function setFeeClaimer(address _claimer) external;
    function setMinFee(uint256 _fee) external;

    // misc
    function trustedTokensCount() external view returns (uint256);
    function adaptersCount() external view returns (uint256);
    function getTokenBonus(address _token) external view returns (uint256);

    // query

    function queryAdapter(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut,
        uint8 _index
    ) external returns (uint256);

    function queryNoSplit(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut,
        uint8[] calldata _options
    ) external view returns (Query memory);

    function queryNoSplit(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut
    ) external view returns (Query memory);

    function findBestPathWithGas(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut,
        uint256 _maxSteps,
        uint256 _gasPrice
    ) external view returns (FormattedOffer memory);

    function findBestPath(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut,
        uint256 _maxSteps
    ) external view returns (FormattedOffer memory);

    // swap

    function swapNoSplit(
        Trade calldata _trade,
        address _to,
        uint256 _fee
    ) external;

    function swapNoSplitFromNATIVE(
        Trade calldata _trade,
        address _to,
        uint256 _fee
    ) external payable;

    function swapNoSplitToNATIVE(
        Trade calldata _trade,
        address _to,
        uint256 _fee
    ) external; 

    function swapNoSplitWithPermit(
        Trade calldata _trade,
        address _to,
        uint256 _fee,
        uint256 _deadline,
        uint8 _v,
        bytes32 _r,
        bytes32 _s
    ) external;

    function swapNoSplitToNATIVEWithPermit(
        Trade calldata _trade,
        address _to,
        uint256 _fee,
        uint256 _deadline,
        uint8 _v,
        bytes32 _r,
        bytes32 _s
    ) external;

}

//
//            __               ___ ___    __            _  
//           (_  | | |\/| |\/|  |   |    (_ \    / /\  |_) 
//           __) |_| |  | |  | _|_  |    __) \/\/ /--\ |   
//

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface ISummitVolumeAdapter {

  event UpdatedRouter(address indexed _router);
  event UpdatedPointsContract(address indexed _pointsContract);

  function addVolume(address _add, uint256 _volume) external;
  function addAdapterVolume(address _adapter, uint256 _volume) external;
  function setRouter(address _router) external;
  function setPointsContract(address _pointsContract) external;
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IUniswapFactory {
    function getPair(address tokenA, address tokenB) external view returns (address pair);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IUniswapPair {
    event Swap(
        address indexed sender,
        uint256 amount0In,
        uint256 amount1In,
        uint256 amount0Out,
        uint256 amount1Out,
        address indexed to
    );

    function factory() external view returns (address);

    function token0() external view returns (address);

    function token1() external view returns (address);

    function getReserves()
        external
        view
        returns (
            uint112 reserve0,
            uint112 reserve1,
            uint32 blockTimestampLast
        );

    function swap(
        uint256 amount0Out,
        uint256 amount1Out,
        address to,
        bytes calldata data
    ) external;
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./IERC20.sol";

interface IVault {
    enum SwapKind {
        GIVEN_IN,
        GIVEN_OUT
    }

    struct SingleSwap {
        bytes32 poolId;
        SwapKind kind;
        address assetIn;
        address assetOut;
        uint256 amount;
        bytes userData;
    }

    struct FundManagement {
        address sender;
        bool fromInternalBalance;
        address payable recipient;
        bool toInternalBalance;
    }

    function swap(
        SingleSwap memory singleSwap,
        FundManagement memory funds,
        uint256 limit,
        uint256 deadline
    ) external payable returns (uint256);

    function getPoolTokens(bytes32 poolId)
        external
        view
        returns (
            IERC20[] memory tokens,
            uint256[] memory balances,
            uint256 lastChangeBlock
        );

    struct BatchSwapStep {
        bytes32 poolId;
        uint256 assetInIndex;
        uint256 assetOutIndex;
        uint256 amount;
        bytes userData;
    }

    function queryBatchSwap(
        SwapKind kind,
        BatchSwapStep[] memory swaps,
        address[] memory assets,
        FundManagement memory funds
    ) external returns (int256[] memory assetDeltas);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./IERC20.sol";

interface IWETH is IERC20 {
    function withdraw(uint256 amount) external;

    function deposit() external payable;
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IWombat {
    // Views
    function quotePotentialSwap(
        address fromToken,
        address totoken,
        int256 fromAmount
    ) external view returns (uint256 potentialOutcome); // Second arg (haircut) is not used

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

    function paused() external view returns (bool);

    // Modifiers
    function swap(
        address fromToken,
        address toToken,
        uint256 fromAmount,
        uint256 minAmountOut,
        address to,
        uint256 deadline
    ) external;

    function pause() external;

    function unpause() external;
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IWooPP {
    function quoteToken() external view returns (address);

    function querySellQuote(address, uint256) external view returns (uint256);

    function querySellBase(address, uint256) external view returns (uint256);

    function sellBase(
        address baseToken,
        uint256 baseAmount,
        uint256 minQuoteAmount,
        address to,
        address rebateTo
    ) external returns (uint256 quoteAmount);

    function sellQuote(
        address baseToken,
        uint256 quoteAmount,
        uint256 minBaseAmount,
        address to,
        address rebateTo
    ) external returns (uint256 baseAmount);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IWooPPV2 {
    /// @notice The quote token address (immutable).
    /// @return address of quote token
    function quoteToken() external view returns (address);

    /// @notice Gets the pool size of the specified token (swap liquidity).
    /// @param token the token address
    /// @return the pool size
    function poolSize(address token) external view returns (uint256);

    /// @notice Query the amount to swap `fromToken` to `toToken`, without checking the pool reserve balance.
    /// @param fromToken the from token
    /// @param toToken the to token
    /// @param fromAmount the amount of `fromToken` to swap
    /// @return toAmount the swapped amount of `toToken`
    function tryQuery(
        address fromToken,
        address toToken,
        uint256 fromAmount
    ) external view returns (uint256 toAmount);

    /// @notice Query the amount to swap `fromToken` to `toToken`, with checking the pool reserve balance.
    /// @dev tx reverts when 'toToken' balance is insufficient.
    /// @param fromToken the from token
    /// @param toToken the to token
    /// @param fromAmount the amount of `fromToken` to swap
    /// @return toAmount the swapped amount of `toToken`
    function query(
        address fromToken,
        address toToken,
        uint256 fromAmount
    ) external view returns (uint256 toAmount);

    /// @notice Swap `fromToken` to `toToken`.
    /// @param fromToken the from token
    /// @param toToken the to token
    /// @param fromAmount the amount of `fromToken` to swap
    /// @param minToAmount the minimum amount of `toToken` to receive
    /// @param to the destination address
    /// @param rebateTo the rebate address (optional, can be address ZERO)
    /// @return realToAmount the amount of toToken to receive
    function swap(
        address fromToken,
        address toToken,
        uint256 fromAmount,
        uint256 minToAmount,
        address to,
        address rebateTo
    ) external returns (uint256 realToAmount);

    /// @notice Deposit the specified token into the liquidity pool of WooPPV2.
    /// @param token the token to deposit
    /// @param amount the deposit amount
    function deposit(address token, uint256 amount) external;
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IxJOE {
    function leave(uint256) external;

    function enter(uint256) external;

    function totalSupply() external view returns (uint256);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IYYDerivative {
    function deposit(uint256 amount) external;

    function depositsEnabled() external view returns (bool);
}

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/access/AccessControl.sol";

/**
 * @dev Contract module which extends the basic access control mechanism of Ownable
 * to include many maintainers, whom only the owner (DEFAULT_ADMIN_ROLE) may add and
 * remove.
 *
 * By default, the owner account will be the one that deploys the contract. This can
 * later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available this modifier:
 * `onlyMaintainer`, which can be applied to your functions to restrict their use to
 * the accounts with the role of maintainer.
 */

abstract contract Maintainable is AccessControl {
    bytes32 public constant MAINTAINER_ROLE = keccak256("MAINTAINER_ROLE");

    constructor() {
        _setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
        _setupRole(MAINTAINER_ROLE, msg.sender);
    }

    function addMaintainer(address addedMaintainer) public virtual {
        grantRole(MAINTAINER_ROLE, addedMaintainer);
    }

    function removeMaintainer(address removedMaintainer) public virtual {
        revokeRole(MAINTAINER_ROLE, removedMaintainer);
    }

    function renounceRole(bytes32 role) public virtual {
        renounceRole(role, msg.sender);
    }

    function transferOwnership(address newOwner) public virtual {
        grantRole(DEFAULT_ADMIN_ROLE, newOwner);
        renounceRole(DEFAULT_ADMIN_ROLE, msg.sender);
    }

    modifier onlyMaintainer() {
        require(hasRole(MAINTAINER_ROLE, msg.sender), "Maintainable: Caller is not a maintainer");
        _;
    }
}

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

import "./SafeERC20.sol";
import "./Maintainable.sol";


abstract contract Recoverable is Maintainable {
    using SafeERC20 for IERC20;

    event Recovered(
        address indexed _asset, 
        uint amount
    );

    /**
     * @notice Recover ERC20 from contract
     * @param _tokenAddress token address
     * @param _tokenAmount amount to recover
     */
    function recoverERC20(address _tokenAddress, uint _tokenAmount) external onlyMaintainer {
        require(_tokenAmount > 0, "Nothing to recover");
        IERC20(_tokenAddress).safeTransfer(msg.sender, _tokenAmount);
        emit Recovered(_tokenAddress, _tokenAmount);
    }

    /**
     * @notice Recover native asset from contract
     * @param _amount amount
     */
    function recoverNATIVE(uint _amount) external onlyMaintainer {
        require(_amount > 0, "Nothing to recover");
        payable(msg.sender).transfer(_amount);
        emit Recovered(address(0), _amount);
    }

}

// This is a simplified version of OpenZepplin's SafeERC20 library
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
pragma experimental ABIEncoderV2;

import "../interface/IERC20.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 ERC20;` statement to your contract,
 * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
 */
library SafeERC20 {
    function safeTransfer(
        IERC20 token,
        address to,
        uint256 value
    ) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
    }

    function safeTransferFrom(
        IERC20 token,
        address from,
        address to,
        uint256 value
    ) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
    }

    function safeApprove(
        IERC20 token,
        address spender,
        uint256 value
    ) internal {
        // safeApprove should only be called when setting an initial allowance,
        // or when resetting it to zero. To increase and decrease it, use
        // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
        // solhint-disable-next-line max-line-length
        require(
            (value == 0) || (token.allowance(address(this), spender) == 0),
            "SafeERC20: approve from non-zero to non-zero allowance"
        );
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
    }

    /**
     * @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.

        // A Solidity high level call has three parts:
        //  1. The target address is checked to verify it contains contract code
        //  2. The call itself is made, and success asserted
        //  3. The return value is decoded, which in turn checks the size of the returned data.
        // solhint-disable-next-line max-line-length

        // solhint-disable-next-line avoid-low-level-calls
        (bool success, bytes memory returndata) = address(token).call(data);
        require(success, "SafeERC20: low-level call failed");

        if (returndata.length > 0) {
            // Return data is optional
            // solhint-disable-next-line max-line-length
            require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
        }
    }
}

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.8.4;

import { Offer, FormattedOffer } from "../interface/ISummitRouter.sol";
import "./TypeConversion.sol";


library OfferUtils {
    using TypeConversion for address;
    using TypeConversion for uint256;
    using TypeConversion for bytes;

    function newOffer(
        uint _amountIn,
        address _tokenIn
    ) internal pure returns (Offer memory offer) {
        offer.amounts = _amountIn.toBytes();
        offer.path = _tokenIn.toBytes();
    }

    /**
     * Makes a deep copy of Offer struct
     */
    function clone(Offer memory _queries) internal pure returns (Offer memory) {
        return Offer(_queries.amounts, _queries.adapters, _queries.path, _queries.gasEstimate);
    }

    /**
     * Appends new elements to the end of Offer struct
     */
    function addToTail(
        Offer memory _queries,
        uint256 _amount,
        address _adapter,
        address _tokenOut,
        uint256 _gasEstimate
    ) internal pure {
        _queries.path = bytes.concat(_queries.path, _tokenOut.toBytes());
        _queries.adapters = bytes.concat(_queries.adapters, _adapter.toBytes());
        _queries.amounts = bytes.concat(_queries.amounts, _amount.toBytes());
        _queries.gasEstimate += _gasEstimate;
    }

    /**
     * Formats elements in the Offer object from byte-arrays to integers and addresses
     */
    function format(Offer memory _queries) internal pure returns (FormattedOffer memory) {
        return
            FormattedOffer(
                _queries.amounts.toUints(),
                _queries.adapters.toAddresses(),
                _queries.path.toAddresses(),
                _queries.gasEstimate
            );
    }

    function getTokenOut(
        Offer memory _offer
    ) internal pure returns (address tokenOut) {
        tokenOut = _offer.path.toAddress(_offer.path.length);  // Last 32 bytes
    }

    function getAmountOut(
        Offer memory _offer
    ) internal pure returns (uint amountOut) {
        amountOut = _offer.amounts.toUint(_offer.path.length);  // Last 32 bytes
    }

}

library FormattedOfferUtils {
    using TypeConversion for address;
    using TypeConversion for uint256;
    using TypeConversion for bytes;

    /**
     * Appends new elements to the end of FormattedOffer
     */
    function addToTail(
        FormattedOffer memory offer, 
        uint256 amountOut, 
        address wrapper,
        address tokenOut,
        uint256 gasEstimate
    ) internal pure {
        offer.amounts = bytes.concat(abi.encodePacked(offer.amounts), amountOut.toBytes()).toUints();
        offer.adapters = bytes.concat(abi.encodePacked(offer.adapters), wrapper.toBytes()).toAddresses();
        offer.path = bytes.concat(abi.encodePacked(offer.path), tokenOut.toBytes()).toAddresses();
        offer.gasEstimate += gasEstimate;
    }

    /**
     * Appends new elements to the beginning of FormattedOffer
     */
    function addToHead(
        FormattedOffer memory offer, 
        uint256 amountOut, 
        address wrapper,
        address tokenOut,
        uint256 gasEstimate
    ) internal pure {
        offer.amounts = bytes.concat(amountOut.toBytes(), abi.encodePacked(offer.amounts)).toUints();
        offer.adapters = bytes.concat(wrapper.toBytes(), abi.encodePacked(offer.adapters)).toAddresses();
        offer.path = bytes.concat(tokenOut.toBytes(), abi.encodePacked(offer.path)).toAddresses();
        offer.gasEstimate += gasEstimate;
    }

    function getAmountOut(FormattedOffer memory offer) internal pure returns (uint256) {
        return offer.amounts[offer.amounts.length - 1];
    }

}

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;


library TypeConversion {

    function toBytes12(address x) internal pure returns (bytes12 y) {
        assembly { y := x }
    }

    function toBytes32(address x) internal pure returns (bytes32 y) {
        assembly { y := x }
    }

    function toAddress(bytes32 x) internal pure returns (address y) {
        assembly { y := x }
    }

    function toBytes(address x) internal pure returns (bytes memory y) {
        y = new bytes(32);
        assembly { mstore(add(y, 32), x) }
    }

    function toBytes(bytes32 x) internal pure returns (bytes memory y) {
        y = new bytes(32);
        assembly { mstore(add(y, 32), x) }
    }

    function toBytes(uint x) internal pure returns (bytes memory y) {
        y = new bytes(32);
        assembly { mstore(add(y, 32), x) }
    }

    function toAddress(
        bytes memory x,
        uint offset
    ) internal pure returns (address y) {
        assembly { y := mload(add(x, offset)) }
    }

    function toUint(
        bytes memory x,
        uint offset
    ) internal pure returns (uint y) {
        assembly { y := mload(add(x, offset)) }
    }

    function toBytes12(
        bytes memory x,
        uint offset
    ) internal pure returns (bytes12 y) {
        assembly { y := mload(add(x, offset)) }
    }

    function toBytes32(
        bytes memory x,
        uint offset
    ) internal pure returns (bytes32 y) {
        assembly { y := mload(add(x, offset)) }
    }

    function toAddresses(
        bytes memory xs
    ) internal pure returns (address[] memory ys) {
        ys = new address[](xs.length/32);
        for (uint i=0; i < xs.length/32; i++) {
            ys[i] = toAddress(xs, i*32 + 32);
        }
    }

    function toUints(
        bytes memory xs
    ) internal pure returns (uint[] memory ys) {
        ys = new uint[](xs.length/32);
        for (uint i=0; i < xs.length/32; i++) {
            ys[i] = toUint(xs, i*32 + 32);
        }
    }

    function toBytes32s(
        bytes memory xs
    ) internal pure returns (bytes32[] memory ys) {
        ys = new bytes32[](xs.length/32);
        for (uint i=0; i < xs.length/32; i++) {
            ys[i] = toBytes32(xs, i*32 + 32);
        }
    }

}

//
//            __               ___ ___    __            _  
//           (_  | | |\/| |\/|  |   |    (_ \    / /\  |_) 
//           __) |_| |  | |  | _|_  |    __) \/\/ /--\ |   
//

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;

import "./interface/IERC20.sol";
import "./interface/IBlast.sol";
import "./lib/SafeERC20.sol";
import "./lib/Maintainable.sol";

abstract contract SummitAdapter is Maintainable {
    using SafeERC20 for IERC20;

    uint256 internal constant UINT_MAX = type(uint256).max;
    uint256 public swapGasEstimate;
    string public name;

    event SummitAdapterSwap(address indexed _tokenFrom, address indexed _tokenTo, uint256 _amountIn, uint256 _amountOut);
    event UpdatedGasEstimate(address indexed _adapter, uint256 _newEstimate);
    event Recovered(address indexed _asset, uint256 amount);

    error AlreadyInitialized();

    constructor(string memory _name, uint256 _gasEstimate) {
        setName(_name);
        setSwapGasEstimate(_gasEstimate);
    }

    bool public initialized = false;
    address public governor;
    function initialize(address _governor) public onlyMaintainer {
        if (initialized) revert AlreadyInitialized();
        initialized = true;

        IBlast blast = IBlast(0x4300000000000000000000000000000000000002);
        blast.configureClaimableGas();
        blast.configureGovernor(_governor);

        governor = _governor;
    }

    function setName(string memory _name) internal {
        require(bytes(_name).length != 0, "Invalid adapter name");
        name = _name;
    }

    function setSwapGasEstimate(uint256 _estimate) public onlyMaintainer {
        require(_estimate != 0, "Invalid gas-estimate");
        swapGasEstimate = _estimate;
        emit UpdatedGasEstimate(address(this), _estimate);
    }

    function revokeAllowance(address _token, address _spender) external onlyMaintainer {
        IERC20(_token).safeApprove(_spender, 0);
    }

    function recoverERC20(address _tokenAddress, uint256 _tokenAmount) external onlyMaintainer {
        require(_tokenAmount > 0, "SummitAdapter: Nothing to recover");
        IERC20(_tokenAddress).safeTransfer(msg.sender, _tokenAmount);
        emit Recovered(_tokenAddress, _tokenAmount);
    }

    function recoverAVAX(uint256 _amount) external onlyMaintainer {
        require(_amount > 0, "SummitAdapter: Nothing to recover");
        payable(msg.sender).transfer(_amount);
        emit Recovered(address(0), _amount);
    }

    function query(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut
    ) external view returns (uint256) {
        return _query(_amountIn, _tokenIn, _tokenOut);
    }

    function swap(
        uint256 _amountIn,
        uint256 _amountOut,
        address _fromToken,
        address _toToken,
        address _to
    ) external virtual {
        uint256 toBal0 = IERC20(_toToken).balanceOf(_to);
        _swap(_amountIn, _amountOut, _fromToken, _toToken, _to);
        uint256 diff = IERC20(_toToken).balanceOf(_to) - toBal0;
        require(diff >= _amountOut, "Insufficient amount-out");
        emit SummitAdapterSwap(_fromToken, _toToken, _amountIn, _amountOut);
    }

    function _returnTo(
        address _token,
        uint256 _amount,
        address _to
    ) internal {
        if (address(this) != _to) IERC20(_token).safeTransfer(_to, _amount);
    }

    function _swap(
        uint256 _amountIn,
        uint256 _amountOut,
        address _fromToken,
        address _toToken,
        address _to
    ) internal virtual;

    function _query(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut
    ) internal view virtual returns (uint256);

    receive() external payable {}
}

//
//            __               ___ ___    __            _  
//           (_  | | |\/| |\/|  |   |    (_ \    / /\  |_) 
//           __) |_| |  | |  | _|_  |    __) \/\/ /--\ |   
//

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;
pragma experimental ABIEncoderV2;

import "./interface/ISummitPoints.sol";
import "./interface/ISummitReferrals.sol";
import "./interface/IBlast.sol";
import "./lib/Maintainable.sol";
import "./lib/SummitViewUtils.sol";
import "./lib/Recoverable.sol";


contract SummitPoints is Maintainable, Recoverable, ISummitPoints {

  address public VOLUME_ADAPTER;
  address public REFERRALS;
  mapping(address => uint256) public SELF_VOLUME;
  mapping(address => uint256) public REF_VOLUME;
  mapping(address => uint256) public ADAPTER_VOLUME;
  mapping(address => address) public DELEGATE;
  mapping(address => bool) public BLACKLISTED;

  uint256 public GLOBAL_BOOST = 0;
  uint256 public BASE_VOLUME_SCALER = 100; // 1% (1 point per $100 in volume initially)
  uint256 public REF_VOLUME_SCALER = 0;
  uint256 public ADAPTER_VOLUME_SCALER = 200; // 2%

  error AlreadyInitialized();
  error ZeroAddress();
  error NotPermitted();
  error InvalidSelfAmount();
  error InvalidRefAmount();
  error InvalidAdapterAmount();

  bool public initialized = false;
  address public governor;
  function initialize(address _governor) public onlyMaintainer {
    if (initialized) revert AlreadyInitialized();
    initialized = true;

    IBlast blast = IBlast(0x4300000000000000000000000000000000000002);
    blast.configureClaimableGas();
    blast.configureGovernor(_governor);

    governor = _governor;
  }

  function setVolumeAdapter(address _volumeAdapter) override public onlyMaintainer {
    emit UpdatedVolumeAdapter(_volumeAdapter);
    VOLUME_ADAPTER = _volumeAdapter;
  }

  function setReferralsContract(address _referrals) override public onlyMaintainer {
    emit UpdatedReferralsContract(_referrals);
    REFERRALS = _referrals;
  }

  function setVolumeScalers(uint256 _refVolumeScaler, uint256 _adapterVolumeScaler) override public onlyMaintainer {
    REF_VOLUME_SCALER = _refVolumeScaler;
    ADAPTER_VOLUME_SCALER = _adapterVolumeScaler;
    emit UpdatedVolumeScalers(_refVolumeScaler, _adapterVolumeScaler);
  }

  function setAdapterDelegate(address _adapter, address _delegate) override public onlyMaintainer {
    DELEGATE[_adapter] = _delegate;
    emit UpdatedAdapterDelegate(_adapter, _delegate);
  }

  function setGlobalBoost(uint256 _boost) override public onlyMaintainer {
    GLOBAL_BOOST = _boost;
    emit UpdatedGlobalBoost(_boost);
  }

  function setBlacklisted(address _add, bool _blacklisted) override public onlyMaintainer {
    BLACKLISTED[_add] = _blacklisted;
    emit UpdatedBlacklisted(_add, _blacklisted);
  }

  function setDelegate(address _user, address _delegate) override public {
    if (_user != msg.sender && DELEGATE[_user] != msg.sender) revert NotPermitted();
    DELEGATE[msg.sender] = _delegate;
    emit UpdatedDelegate(msg.sender, _user, _delegate);
  }

  function summitTeamGivePoints(address _add, uint256 _volume) override public onlyMaintainer {
    SELF_VOLUME[_add] += _volume;
    emit SummitTeamGivenPoints(_add, _volume);    
  }

  function addVolume(address _add, uint256 _volume) override public {
    if (msg.sender != VOLUME_ADAPTER) revert NotPermitted();

    if (GLOBAL_BOOST > 0) {
      _volume = (_volume * (10000 + GLOBAL_BOOST)) / 10000;
    }

    SELF_VOLUME[_add] += _volume;
    emit AddedUserVolume(_add, _volume);

    address referrer = REFERRALS == address(0) ? address(0) : ISummitReferrals(REFERRALS).getReferrer(_add);
    if (referrer != address(0)) {
      REF_VOLUME[referrer] += _volume;
      emit AddedReferrerVolume(referrer, _add, _volume);
    }
  }

  function addAdapterVolume(address _adapter, uint256 _volume) override public {
    if (msg.sender != VOLUME_ADAPTER) revert NotPermitted();

    if (GLOBAL_BOOST > 0) {
      _volume = (_volume * (10000 + GLOBAL_BOOST)) / 10000;
    }

    ADAPTER_VOLUME[_adapter] += _volume;
    emit AddedAdapterVolume(_adapter, _volume);
  }

  function transferVolume(address _from, address _to, uint256 _selfAmount, uint256 _refAmount, uint256 _adapterAmount) override public {
    if (_to == address(0)) revert ZeroAddress();
    if (_from != msg.sender && DELEGATE[_from] != msg.sender) revert NotPermitted();
    if (_selfAmount > SELF_VOLUME[_from]) revert InvalidSelfAmount();
    if (_refAmount > REF_VOLUME[_from]) revert InvalidRefAmount();
    if (_adapterAmount > ADAPTER_VOLUME[_from]) revert InvalidAdapterAmount();
    
    SELF_VOLUME[_from] -= _selfAmount;
    SELF_VOLUME[_to] += _selfAmount;
    
    REF_VOLUME[_from] -= _refAmount;
    REF_VOLUME[_to] += _refAmount;

    ADAPTER_VOLUME[_from] -= _adapterAmount;
    ADAPTER_VOLUME[_to] += _adapterAmount;

    emit TransferredVolume(msg.sender, _from, _to, _selfAmount, _refAmount, _adapterAmount);
  }

  function getVolume(address _add) override public view returns (uint256 selfVolume, uint256 refVolume, uint256 adapterVolume) {
    return (
      SELF_VOLUME[_add],
      REF_VOLUME[_add],
      ADAPTER_VOLUME[_add]
    );
  }

  function getPoints(address _add) override public view returns (uint256 pointsFromSelf, uint256 pointsFromRef, uint256 pointsFromAdapter, uint256 pointsTotal) {
    if (BLACKLISTED[_add]) return (0, 0, 0, 0);
    uint256 userSelfVolMult = REFERRALS == address(0) ? 10000 : ISummitReferrals(REFERRALS).getSelfVolumeMultiplier(_add);
    uint256 userRefVolMult = REFERRALS == address(0) ? 0 : ISummitReferrals(REFERRALS).getRefVolumeBonusMultiplier(_add);
    pointsFromSelf = (SELF_VOLUME[_add] * userSelfVolMult * BASE_VOLUME_SCALER) / (10000 * 10000);
    pointsFromRef = (REF_VOLUME[_add] * (REF_VOLUME_SCALER + userRefVolMult) * BASE_VOLUME_SCALER) / (10000 * 10000);
    pointsFromAdapter = (ADAPTER_VOLUME[_add] * ADAPTER_VOLUME_SCALER * BASE_VOLUME_SCALER) / (10000 * 10000);
    pointsTotal = pointsFromSelf + pointsFromRef + pointsFromAdapter;
  }
}

//
//            __               ___ ___    __            _  
//           (_  | | |\/| |\/|  |   |    (_ \    / /\  |_) 
//           __) |_| |  | |  | _|_  |    __) \/\/ /--\ |   
//

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;
pragma experimental ABIEncoderV2;

import "./interface/ISummitRouter.sol";
import "./interface/IAdapter.sol";
import "./interface/IBlast.sol";
import "./interface/IERC20.sol";
import "./interface/IWETH.sol";
import "./lib/SafeERC20.sol";
import "./lib/Maintainable.sol";
import "./lib/SummitViewUtils.sol";
import "./lib/Recoverable.sol";
import "./interface/ISummitVolumeAdapter.sol";
import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";

contract SummitRouter is Maintainable, Recoverable, ISummitRouter {
    using SafeERC20 for IERC20;
    using OfferUtils for Offer;
    using EnumerableSet for EnumerableSet.AddressSet;

    address public immutable WNATIVE;
    address public constant NATIVE = address(0);
    string public constant NAME = "SummitRouter";
    uint256 public constant FEE_DENOMINATOR = 1e4;
    uint256 public MIN_FEE = 0;
    address public FEE_CLAIMER;
    address[] public TRUSTED_TOKENS;
    mapping(address => uint256) public TOKEN_VOLUME_MULTIPLIERS; // (token amount * tokenVolumeMultiplier[token]) / 1e12
    mapping(address => uint256) public TOKEN_VOLUME_BONUS;
    address[] public ADAPTERS;
    address public VOLUME_ADAPTER;

    error AlreadyInitialized();

    constructor(
        address[] memory _adapters,
        address[] memory _trustedTokens,
        address _feeClaimer,
        address _wrapped_native
    ) {
        setAllowanceForWrapping(_wrapped_native);
        setTrustedTokens(_trustedTokens);
        setFeeClaimer(_feeClaimer);
        setAdapters(_adapters);
        WNATIVE = _wrapped_native;
    }

    bool public initialized = false;
    address public governor;
    function initialize(address _governor) public onlyMaintainer {
        if (initialized) revert AlreadyInitialized();
        initialized = true;

        IBlast blast = IBlast(0x4300000000000000000000000000000000000002);
        blast.configureClaimableGas();
        blast.configureGovernor(_governor);

        governor = _governor;
    }

    // -- SETTERS --

    function setAllowanceForWrapping(address _wnative) public onlyMaintainer {
        IERC20(_wnative).safeApprove(_wnative, type(uint256).max);
    }

    function setTrustedTokens(address[] memory _trustedTokens) override public onlyMaintainer {
        emit UpdatedTrustedTokens(_trustedTokens);
        TRUSTED_TOKENS = _trustedTokens;
    }

    function setTokenVolumeMultipliers(address[] memory _tokens, uint256[] memory _volumeMultipliers) public onlyMaintainer {
        require(_tokens.length == _volumeMultipliers.length, "SummitRouter: Array length mismatch");
        emit UpdatedTokenVolumeMultipliers(_tokens, _volumeMultipliers);
        for (uint256 i = 0; i < _tokens.length; i++) {
            TOKEN_VOLUME_MULTIPLIERS[_tokens[i]] = _volumeMultipliers[i];
        }
    }

    function setTokenBonusMultipliers(address[] memory _tokens, uint256[] memory _multipliers) public onlyMaintainer {
        require(_tokens.length == _multipliers.length, "SummitRouter: Array length mismatch");
        emit UpdatedTokenBonusMultipliers(_tokens, _multipliers);
        for (uint256 i = 0; i < _tokens.length; i++) {
            TOKEN_VOLUME_BONUS[_tokens[i]] = _multipliers[i];
        }
    }

    function setAdapters(address[] memory _adapters) override public onlyMaintainer {
        emit UpdatedAdapters(_adapters);
        ADAPTERS = _adapters;
    }

    function setVolumeAdapter(address _volumeAdapter) override public onlyMaintainer {
        emit UpdatedVolumeAdapter(_volumeAdapter);
        VOLUME_ADAPTER = _volumeAdapter;
    }

    function setMinFee(uint256 _fee) override external onlyMaintainer {
        emit UpdatedMinFee(MIN_FEE, _fee);
        MIN_FEE = _fee;
    }

    function setFeeClaimer(address _claimer) override public onlyMaintainer {
        emit UpdatedFeeClaimer(FEE_CLAIMER, _claimer);
        FEE_CLAIMER = _claimer;
    }

    //  -- GENERAL --

    function trustedTokensCount() override external view returns (uint256) {
        return TRUSTED_TOKENS.length;
    }

    function adaptersCount() override external view returns (uint256) {
        return ADAPTERS.length;
    }

    // Fallback
    receive() external payable {}

    // -- HELPERS --

    function _applyFee(uint256 _amountIn, uint256 _fee) internal view returns (uint256) {
        require(_fee >= MIN_FEE, "SummitRouter: Insufficient fee");
        return (_amountIn * (FEE_DENOMINATOR - _fee)) / FEE_DENOMINATOR;
    }

    function _wrap(uint256 _amount) internal {
        IWETH(WNATIVE).deposit{ value: _amount }();
    }

    function _unwrap(uint256 _amount) internal {
        IWETH(WNATIVE).withdraw(_amount);
    }

    /**
     * @notice Return tokens to user
     * @dev Pass address(0) for NATIVE
     * @param _token address
     * @param _amount tokens to return
     * @param _to address where funds should be sent to
     */
    function _returnTokensTo(
        address _token,
        uint256 _amount,
        address _to
    ) internal {
        if (address(this) != _to) {
            if (_token == NATIVE) {
                payable(_to).transfer(_amount);
            } else {
                IERC20(_token).safeTransfer(_to, _amount);
            }
        }
    }

    function _transferFrom(address token, address _from, address _to, uint _amount) internal {
        if (_from != address(this))
            IERC20(token).safeTransferFrom(_from, _to, _amount);
        else
            IERC20(token).safeTransfer(_to, _amount);
    }
    
    // -- QUERIES --

    /**
     * Query single adapter
     */
    function queryAdapter(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut,
        uint8 _index
    ) override external view returns (uint256) {
        IAdapter _adapter = IAdapter(ADAPTERS[_index]);
        uint256 amountOut = _adapter.query(_amountIn, _tokenIn, _tokenOut);
        return amountOut;
    }

    /**
     * Query specified adapters
     */
    function queryNoSplit(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut,
        uint8[] calldata _options
    ) override public view returns (Query memory) {
        Query memory bestQuery;
        for (uint8 i; i < _options.length; i++) {
            address _adapter = ADAPTERS[_options[i]];
            uint256 amountOut = IAdapter(_adapter).query(_amountIn, _tokenIn, _tokenOut);
            if (i == 0 || amountOut > bestQuery.amountOut) {
                bestQuery = Query(_adapter, _tokenIn, _tokenOut, amountOut);
            }
        }
        return bestQuery;
    }

    /**
     * Query all adapters
     */
    function queryNoSplit(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut
    ) override public view returns (Query memory) {
        Query memory bestQuery;
        for (uint8 i; i < ADAPTERS.length; i++) {
            address _adapter = ADAPTERS[i];
            uint256 amountOut = IAdapter(_adapter).query(_amountIn, _tokenIn, _tokenOut);
            if (i == 0 || amountOut > bestQuery.amountOut) {
                bestQuery = Query(_adapter, _tokenIn, _tokenOut, amountOut);
            }
        }
        return bestQuery;
    }

    /**
     * Return path with best returns between two tokens
     * Takes gas-cost into account
     */
    function findBestPathWithGas(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut,
        uint256 _maxSteps,
        uint256 _gasPrice
    ) override external view returns (FormattedOffer memory) {
        require(_maxSteps > 0 && _maxSteps < 5, "SummitRouter: Invalid max-steps");
        Offer memory queries = OfferUtils.newOffer(_amountIn, _tokenIn);
        uint256 gasPriceInExitTkn = _gasPrice > 0 ? getGasPriceInExitTkn(_gasPrice, _tokenOut) : 0;
        queries = _findBestPath(_amountIn, _tokenIn, _tokenOut, _maxSteps, queries, gasPriceInExitTkn);
        if (queries.adapters.length == 0) {
            queries.amounts = "";
            queries.path = "";
        }
        return queries.format();
    }

    // Find the market price between gas-asset(native) and token-out and express gas price in token-out
    function getGasPriceInExitTkn(uint256 _gasPrice, address _tokenOut) internal view returns (uint256 price) {
        // Avoid low-liquidity price appreciation (https://github.com/yieldsummit/summit-aggregator/issues/20)
        FormattedOffer memory gasQuery = findBestPath(1e18, WNATIVE, _tokenOut, 2);
        if (gasQuery.path.length != 0) {
            // Leave result in nWei to preserve precision for assets with low decimal places
            price = (gasQuery.amounts[gasQuery.amounts.length - 1] * _gasPrice) / 1e9;
        }
    }

    /**
     * Return path with best returns between two tokens
     */
    function findBestPath(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut,
        uint256 _maxSteps
    ) override public view returns (FormattedOffer memory) {
        require(_maxSteps > 0 && _maxSteps < 5, "SummitRouter: Invalid max-steps");
        Offer memory queries = OfferUtils.newOffer(_amountIn, _tokenIn);
        queries = _findBestPath(_amountIn, _tokenIn, _tokenOut, _maxSteps, queries, 0);
        // If no paths are found return empty struct
        if (queries.adapters.length == 0) {
            queries.amounts = "";
            queries.path = "";
        }
        return queries.format();
    }

    function _findBestPath(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut,
        uint256 _maxSteps,
        Offer memory _queries,
        uint256 _tknOutPriceNwei
    ) internal view returns (Offer memory) {
        Offer memory bestOption = _queries.clone();
        uint256 bestAmountOut;
        uint256 gasEstimate;
        bool withGas = _tknOutPriceNwei != 0;

        // First check if there is a path directly from tokenIn to tokenOut
        Query memory queryDirect = queryNoSplit(_amountIn, _tokenIn, _tokenOut);

        if (queryDirect.amountOut != 0) {
            if (withGas) {
                gasEstimate = IAdapter(queryDirect.adapter).swapGasEstimate();
            }
            bestOption.addToTail(queryDirect.amountOut, queryDirect.adapter, queryDirect.tokenOut, gasEstimate);
            bestAmountOut = queryDirect.amountOut;
        }
        // Only check the rest if they would go beyond step limit (Need at least 2 more steps)
        if (_maxSteps > 1 && _queries.adapters.length / 32 <= _maxSteps - 2) {
            // Check for paths that pass through trusted tokens
            for (uint256 i = 0; i < TRUSTED_TOKENS.length; i++) {
                if (_tokenIn == TRUSTED_TOKENS[i]) {
                    continue;
                }
                // Loop through all adapters to find the best one for swapping tokenIn for one of the trusted tokens
                Query memory bestSwap = queryNoSplit(_amountIn, _tokenIn, TRUSTED_TOKENS[i]);
                if (bestSwap.amountOut == 0) {
                    continue;
                }
                // Explore options that connect the current path to the tokenOut
                Offer memory newOffer = _queries.clone();
                if (withGas) {
                    gasEstimate = IAdapter(bestSwap.adapter).swapGasEstimate();
                }
                newOffer.addToTail(bestSwap.amountOut, bestSwap.adapter, bestSwap.tokenOut, gasEstimate);
                newOffer = _findBestPath(
                    bestSwap.amountOut,
                    TRUSTED_TOKENS[i],
                    _tokenOut,
                    _maxSteps,
                    newOffer,
                    _tknOutPriceNwei
                ); // Recursive step
                address tokenOut = newOffer.getTokenOut();
                uint256 amountOut = newOffer.getAmountOut();
                // Check that the last token in the path is the tokenOut and update the new best option if neccesary
                if (_tokenOut == tokenOut && amountOut > bestAmountOut) {
                    if (newOffer.gasEstimate > bestOption.gasEstimate) {
                        uint256 gasCostDiff = (_tknOutPriceNwei * (newOffer.gasEstimate - bestOption.gasEstimate)) /
                            1e9;
                        uint256 priceDiff = amountOut - bestAmountOut;
                        if (gasCostDiff > priceDiff) {
                            continue;
                        }
                    }
                    bestAmountOut = amountOut;
                    bestOption = newOffer;
                }
            }
        }
        return bestOption;
    }

    // -- VOLUME --

    function getTokenBonus(address _token) override public view returns (uint256) {
        if (_token == address(0)) return 0;
        return TOKEN_VOLUME_BONUS[_token];
    }

    function _applyUserVolume(
        address _from,
        address[] memory _path, 
        uint256[] memory _amounts
    ) internal returns (uint256) {
        if (VOLUME_ADAPTER == address(0)) return 0;

        uint256 volume = 0;
        uint256 bonus = 0;
        for (uint256 i = 0; i < _path.length; i++) {
            if (bonus == 0 && TOKEN_VOLUME_BONUS[_path[i]] != 0) {
                bonus = TOKEN_VOLUME_BONUS[_path[i]];
            }
            if (volume == 0 && TOKEN_VOLUME_MULTIPLIERS[_path[i]] != 0) {
                volume = TOKEN_VOLUME_MULTIPLIERS[_path[i]] * _amounts[i] / 1e12;
            }
        }

        if (volume > 0 && VOLUME_ADAPTER != address(0)) {
            ISummitVolumeAdapter(VOLUME_ADAPTER).addVolume(_from, (volume * (10000 + bonus)) / 10000);
        }

        return volume;
    }

    function _applyAdapterVolume(
        address _adapter,
        uint256 _volume
    ) internal {
        if (_volume == 0 || VOLUME_ADAPTER == address(0)) return;
        ISummitVolumeAdapter(VOLUME_ADAPTER).addAdapterVolume(_adapter, _volume);
    }

    // -- SWAPPERS --

    function _swapNoSplit(
        Trade calldata _trade,
        address _from,
        address _to,
        uint256 _fee
    ) internal returns (uint256) {
        uint256[] memory amounts = new uint256[](_trade.path.length);
        if (_fee > 0 || MIN_FEE > 0) {
            // Transfer fees to the claimer account and decrease initial amount
            amounts[0] = _applyFee(_trade.amountIn, _fee);
            _transferFrom(_trade.path[0], _from, address(this), _trade.amountIn - amounts[0]);
            _transferFrom(_trade.path[0], address(this), FEE_CLAIMER, _trade.amountIn - amounts[0]);
        } else {
            amounts[0] = _trade.amountIn;
        }
        _transferFrom(_trade.path[0], _from, _trade.adapters[0], amounts[0]);

        // Get amounts that will be swapped
        for (uint256 i = 0; i < _trade.adapters.length; i++) {
            amounts[i + 1] = IAdapter(_trade.adapters[i]).query(amounts[i], _trade.path[i], _trade.path[i + 1]);
        }

        // Add volume to user
        uint256 volume = _applyUserVolume(msg.sender, _trade.path, amounts);

        require(amounts[amounts.length - 1] >= _trade.amountOut, "SummitRouter: Insufficient output amount");
        for (uint256 i = 0; i < _trade.adapters.length; i++) {
            _applyAdapterVolume(_trade.adapters[i], volume);

            // All adapters should transfer output token to the following target
            // All targets are the adapters, expect for the last swap where tokens are sent out
            address targetAddress = i < _trade.adapters.length - 1 ? _trade.adapters[i + 1] : _to;
            IAdapter(_trade.adapters[i]).swap(
                amounts[i],
                amounts[i + 1],
                _trade.path[i],
                _trade.path[i + 1],
                targetAddress
            );
        }

        

        emit SummitSwap(_trade.path[0], _trade.path[_trade.path.length - 1], _trade.amountIn, amounts[amounts.length - 1]);
        return amounts[amounts.length - 1];
    }

    function swapNoSplit(
        Trade calldata _trade,
        address _to,
        uint256 _fee
    ) override public {
        _swapNoSplit(_trade, msg.sender, _to, _fee);
    }

    function swapNoSplitFromNATIVE(
        Trade calldata _trade,
        address _to,
        uint256 _fee
    ) override external payable {
        require(_trade.path[0] == WNATIVE, "SummitRouter: Path needs to begin with WNATIVE");
        _wrap(_trade.amountIn);
        _swapNoSplit(_trade, address(this), _to, _fee);
    }

    function swapNoSplitToNATIVE(
        Trade calldata _trade,
        address _to,
        uint256 _fee
    ) override public {
        require(_trade.path[_trade.path.length - 1] == WNATIVE, "SummitRouter: Path needs to end with WNATIVE");
        uint256 returnAmount = _swapNoSplit(_trade, msg.sender, address(this), _fee);
        _unwrap(returnAmount);
        _returnTokensTo(NATIVE, returnAmount, _to);
    }

    /**
     * Swap token to token without the need to approve the first token
     */
    function swapNoSplitWithPermit(
        Trade calldata _trade,
        address _to,
        uint256 _fee,
        uint256 _deadline,
        uint8 _v,
        bytes32 _r,
        bytes32 _s
    ) override external {
        IERC20(_trade.path[0]).permit(msg.sender, address(this), _trade.amountIn, _deadline, _v, _r, _s);
        swapNoSplit(_trade, _to, _fee);
    }

    /**
     * Swap token to NATIVE without the need to approve the first token
     */
    function swapNoSplitToNATIVEWithPermit(
        Trade calldata _trade,
        address _to,
        uint256 _fee,
        uint256 _deadline,
        uint8 _v,
        bytes32 _r,
        bytes32 _s
    ) override external {
        IERC20(_trade.path[0]).permit(msg.sender, address(this), _trade.amountIn, _deadline, _v, _r, _s);
        swapNoSplitToNATIVE(_trade, _to, _fee);
    }
}

//
//            __               ___ ___    __            _  
//           (_  | | |\/| |\/|  |   |    (_ \    / /\  |_) 
//           __) |_| |  | |  | _|_  |    __) \/\/ /--\ |   
//

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;
pragma experimental ABIEncoderV2;

import "./interface/ISummitVolumeAdapter.sol";
import "./interface/ISummitPoints.sol";
import "./interface/IAdapter.sol";
import "./interface/IBlast.sol";
import "./interface/IERC20.sol";
import "./interface/IWETH.sol";
import "./lib/SafeERC20.sol";
import "./lib/Maintainable.sol";
import "./lib/SummitViewUtils.sol";
import "./lib/Recoverable.sol";
import "./lib/SafeERC20.sol";


contract SummitVolumeAdapterV1 is Maintainable, Recoverable, ISummitVolumeAdapter {

  address public ROUTER;
  address public POINTS_CONTRACT;

  error OnlyRouter();
  error AlreadyInitialized();

  bool public initialized = false;
  address public governor;
  function initialize(address _governor) public onlyMaintainer {
    if (initialized) revert AlreadyInitialized();
    initialized = true;

    IBlast blast = IBlast(0x4300000000000000000000000000000000000002);
    blast.configureClaimableGas();
    blast.configureGovernor(_governor);

    governor = _governor;
  }


  function setRouter(address _router) override public onlyMaintainer {
    emit UpdatedRouter(_router);
    ROUTER = _router;
  }

  function setPointsContract(address _pointsContract) override public onlyMaintainer {
    emit UpdatedPointsContract(_pointsContract);
    POINTS_CONTRACT = _pointsContract;
  }

  function addVolume(address _add, uint256 _volume) override public {
    if (msg.sender != ROUTER) revert OnlyRouter();
    if (POINTS_CONTRACT == address(0)) return;
    ISummitPoints(POINTS_CONTRACT).addVolume(_add, _volume);
  }

  function addAdapterVolume(address _add, uint256 _volume) override public {
    if (msg.sender != ROUTER) revert OnlyRouter();
    if (POINTS_CONTRACT == address(0)) return;
    ISummitPoints(POINTS_CONTRACT).addAdapterVolume(_add, _volume);
  }
}

//
//            __               ___ ___    __            _  
//           (_  | | |\/| |\/|  |   |    (_ \    / /\  |_) 
//           __) |_| |  | |  | _|_  |    __) \/\/ /--\ |   
//

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;

import "./SummitAdapter.sol";

abstract contract SummitWrapper is SummitAdapter {

    constructor(string memory name, uint256 gasEstimate) SummitAdapter(name, gasEstimate) {}

    function getTokensIn() external view virtual returns (address[] memory);
    function getTokensOut() external view virtual returns (address[] memory);
    function getWrappedToken() external view virtual returns (address);

}

//
//            __               ___ ___    __            _  
//           (_  | | |\/| |\/|  |   |    (_ \    / /\  |_) 
//           __) |_| |  | |  | _|_  |    __) \/\/ /--\ |   
//

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;

import "../interface/IUniswapFactory.sol";
import "../interface/IUniswapPair.sol";
import "../interface/IERC20.sol";
import "../lib/SafeERC20.sol";
import "../SummitAdapter.sol";

contract DummyAdapter is SummitAdapter {
    using SafeERC20 for IERC20;

    IERC20 public A;
    IERC20 public B;

    uint256 AtoBPrice = 50;

    constructor(
        string memory _name,
        uint256 _swapGasEstimate,
        IERC20 tokenA,
        IERC20 tokenB
    ) SummitAdapter(_name, _swapGasEstimate) {
      A = tokenA;
      B = tokenB;
    }

    function _getAmountOut(
        uint256 _amountIn,
        address _tokenIn
    ) internal view returns (uint256 amountOut) {
      if (_tokenIn == address(A)) return _amountIn * AtoBPrice;
      return _amountIn / AtoBPrice;
    }

    function _query(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut
    ) internal view override returns (uint256 amountOut) {
      return _getAmountOut(_amountIn, _tokenIn);
    }

    function _swap(
        uint256 _amountIn,
        uint256 _amountOut,
        address _tokenIn,
        address _tokenOut,
        address to
    ) internal override {
        IERC20(_tokenOut).safeTransfer(to, _getAmountOut(_amountIn, _tokenIn));
    }
}

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

import "../SummitWrapper.sol";


interface ISomeExternalContract {
    function getWhitelistedTokens() external view returns (address[] memory);
    function getWrappedToken() external view returns (address);
    
    function queryMint(address from, uint amount) external view returns (uint256);
    function queryBurn(address to, uint amount) external view returns (uint256);

    function mintWrappedToken(address from, uint amount) external;
    function burnWrappedToken(address to, uint amount) external;
}

contract TestWrapper is SummitWrapper {
    using SafeERC20 for IERC20;

    address internal immutable someExternalContract;
    mapping(address => bool) internal isWhitelisted;
    address internal immutable wrappedToken;
    address[] internal whitelistedTokens;

    constructor(
        string memory _name, 
        uint256 _gasEstimate, 
        address _someExternalContract
    ) SummitWrapper(_name, _gasEstimate) {
        whitelistedTokens = ISomeExternalContract(_someExternalContract).getWhitelistedTokens();
        wrappedToken = ISomeExternalContract(_someExternalContract).getWrappedToken();
        someExternalContract = _someExternalContract;
    }

    function setWhitelistedTokens(address[] memory tokens) public onlyMaintainer {
        for (uint i = 0; i < whitelistedTokens.length; i++) {
            isWhitelisted[whitelistedTokens[i]] = false;
        }
        whitelistedTokens = tokens;
        for (uint i = 0; i < tokens.length; i++) {
            isWhitelisted[tokens[i]] = true;
        }
    }

    function getTokensIn() override external view returns (address[] memory) {
        return whitelistedTokens;
    }

    function getTokensOut() override external view returns (address[] memory) {
        return whitelistedTokens;
    }

    function getWrappedToken() override external view returns (address) {
        return wrappedToken;
    }

    function _query(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut
    ) override internal view returns (uint256) {
        if (_tokenIn == wrappedToken && isWhitelisted[_tokenOut]) {
            return ISomeExternalContract(someExternalContract).queryBurn(_tokenOut, _amountIn);
        } else if (_tokenOut == wrappedToken && isWhitelisted[_tokenIn]) {
            return ISomeExternalContract(someExternalContract).queryMint(_tokenIn, _amountIn);
        } else {
            return 0;
        }
    }

    function _swap(
        uint256 _amountIn,
        uint256 _amountOut,
        address _tokenIn,
        address _tokenOut,
        address _to
    ) override internal {
        if (_tokenIn == wrappedToken && isWhitelisted[_tokenOut]) {
            IERC20(_tokenOut).safeTransfer(someExternalContract, _amountIn);
            ISomeExternalContract(someExternalContract).burnWrappedToken(_tokenOut, _amountIn);
        } else if (_tokenOut == wrappedToken && isWhitelisted[_tokenIn]) {
            IERC20(_tokenIn).safeTransfer(someExternalContract, _amountIn);
            ISomeExternalContract(someExternalContract).mintWrappedToken(_tokenIn, _amountIn);
        } else {
            revert("Invalid token pair");
        }
        _returnTo(_tokenOut, _amountOut, _to);
    }

}

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

import "../SummitWrapper.sol";
import "../interface/IGmxVault.sol";
import "../interface/IGlpManager.sol";
import "../interface/IGmxRewardRouter.sol";
import "../interface/IERC20.sol";
import "../lib/SafeERC20.sol";

contract GlpWrapper is SummitWrapper {
    using SafeERC20 for IERC20;

    uint256 public constant BASIS_POINTS_DIVISOR = 1e4;
    uint256 public constant PRICE_PRECISION = 1e30;

    address public immutable USDG;
    address public immutable GLP;
    address public immutable sGLP;
    address public immutable vault;
    address public immutable rewardRouter;
    address public immutable glpManager;
    address public immutable vaultUtils;

    mapping(address => bool) internal isWhitelisted;
    address[] internal whitelistedTokens;

    constructor(
        string memory _name,
        uint256 _gasEstimate,
        address _gmxRewardRouter,
        address _glp,
        address _sGlp
    ) SummitWrapper(_name, _gasEstimate) {
        address gmxGLPManager = IGmxRewardRouter(_gmxRewardRouter).glpManager();
        address gmxVault = IGlpManager(gmxGLPManager).vault();
        USDG = IGmxVault(gmxVault).usdg();
        vaultUtils = address(IGmxVault(gmxVault).vaultUtils());
        rewardRouter = _gmxRewardRouter;
        vault = gmxVault;
        glpManager = gmxGLPManager;
        GLP = _glp;
        sGLP = _sGlp;
    }

    function setWhitelistedTokens(address[] memory tokens) public onlyMaintainer {
        for (uint256 i = 0; i < whitelistedTokens.length; i++) {
            isWhitelisted[whitelistedTokens[i]] = false;
        }
        whitelistedTokens = tokens;
        for (uint256 i = 0; i < tokens.length; i++) {
            isWhitelisted[tokens[i]] = true;
        }
    }

    function getTokensIn() external view override returns (address[] memory) {
        return whitelistedTokens;
    }

    function getTokensOut() external view override returns (address[] memory) {
        return whitelistedTokens;
    }

    function getWrappedToken() external view override returns (address) {
        return sGLP;
    }

    function _query(
        uint256 _amountIn,
        address _tokenIn,
        address _tokenOut
    ) internal view override returns (uint256 amountOut) {
        return (_tokenOut == sGLP) ? _quoteBuyGLP(_tokenIn, _amountIn) : _quoteSellGLP(_tokenOut, _amountIn);
    }

    function _quoteBuyGLP(address _tokenIn, uint256 _amountIn) internal view returns (uint256 amountOut) {
        uint256 aumInUsdg = IGlpManager(glpManager).getAumInUsdg(true);
        uint256 glpSupply = IERC20(GLP).totalSupply();
        uint256 price = IGmxVault(vault).getMinPrice(_tokenIn);
        uint256 usdgAmount = _calculateBuyUsdg(_amountIn, price, _tokenIn);
        amountOut = aumInUsdg == 0 ? usdgAmount : (usdgAmount * glpSupply) / aumInUsdg;
    }

    function _calculateBuyUsdg(
        uint256 _amountIn,
        uint256 _price,
        address _tokenIn
    ) internal view returns (uint256 amountOut) {
        amountOut = (_amountIn * _price) / PRICE_PRECISION;
        amountOut = IGmxVault(vault).adjustForDecimals(amountOut, _tokenIn, USDG);
        uint256 feeBasisPoints = IGmxVaultUtils(vaultUtils).getBuyUsdgFeeBasisPoints(_tokenIn, amountOut);
        uint256 amountAfterFees = (_amountIn * (BASIS_POINTS_DIVISOR - feeBasisPoints)) / BASIS_POINTS_DIVISOR;
        amountOut = (amountAfterFees * _price) / PRICE_PRECISION;
        amountOut = IGmxVault(vault).adjustForDecimals(amountOut, _tokenIn, USDG);
    }

    function _quoteSellGLP(address _tokenOut, uint256 _amountIn) internal view returns (uint256 amountOut) {
        uint256 aumInUsdg = IGlpManager(glpManager).getAumInUsdg(false);
        uint256 glpSupply = IERC20(GLP).totalSupply();
        uint256 usdgAmount = (_amountIn * aumInUsdg) / glpSupply;
        uint256 redemptionAmount = IGmxVault(vault).getRedemptionAmount(_tokenOut, usdgAmount);
        uint256 feeBasisPoints = calculateSellUsdgFeeBasisPoints(_tokenOut, usdgAmount);
        amountOut = (redemptionAmount * (BASIS_POINTS_DIVISOR - feeBasisPoints)) / BASIS_POINTS_DIVISOR;
    }

    function calculateSellUsdgFeeBasisPoints(address _token, uint256 _usdgDelta) internal view returns (uint256) {
        uint feeBasisPoints = IGmxVault(vault).mintBurnFeeBasisPoints();
        uint taxBasisPoints = IGmxVault(vault).taxBasisPoints();
        if (!IGmxVault(vault).hasDynamicFees()) {
            return feeBasisPoints;
        }

        uint256 initialAmount = IGmxVault(vault).usdgAmounts(_token) - _usdgDelta;
        uint256 nextAmount = _usdgDelta > initialAmount ? 0 : initialAmount - _usdgDelta;

        uint256 targetAmount = IGmxVault(vault).getTargetUsdgAmount(_token);
        if (targetAmount == 0) {
            return feeBasisPoints;
        }

        uint256 initialDiff = initialAmount > targetAmount
            ? initialAmount - targetAmount
            : targetAmount - initialAmount;
        uint256 nextDiff = nextAmount > targetAmount ? nextAmount - targetAmount : targetAmount - nextAmount;

        if (nextDiff < initialDiff) {
            uint256 rebateBps = (taxBasisPoints * initialDiff) / targetAmount;
            return rebateBps > feeBasisPoints ? 0 : feeBasisPoints - rebateBps;
        }

        uint256 averageDiff = (initialDiff + nextDiff) / 2;
        if (averageDiff > targetAmount) {
            averageDiff = targetAmount;
        }
        uint256 taxBps = (taxBasisPoints * averageDiff) / targetAmount;
        return feeBasisPoints + taxBps;
    }

    function _swap(
        uint256 _amountIn,
        uint256 _amountOut,
        address _tokenIn,
        address _tokenOut,
        address _to
    ) internal override {}

    function swap(
        uint256 _amountIn,
        uint256 _amountOut,
        address _fromToken,
        address _toToken,
        address _to
    ) external override {
        uint256 toBalanceBefore = IERC20(_toToken).balanceOf(_to);
        if (_toToken == sGLP) {
            IERC20(_fromToken).approve(glpManager, _amountIn);
            uint256 amount = IGmxRewardRouter(rewardRouter).mintAndStakeGlp(_fromToken, _amountIn, 0, _amountOut);
            _returnTo(sGLP, amount, _to);
        } else {
            IGmxRewardRouter(rewardRouter).unstakeAndRedeemGlp(_toToken, _amountIn, _amountOut, _to);
        }
        uint256 diff = IERC20(_toToken).balanceOf(_to) - toBalanceBefore;
        require(diff >= _amountOut, "Insufficient amount-out");
        emit SummitAdapterSwap(_fromToken, _toToken, _amountIn, _amountOut);
    }
}

Settings
{
  "optimizer": {
    "enabled": true,
    "runs": 999
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  }
}

Contract Security Audit

Contract ABI

API
[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AlreadyInitialized","type":"error"},{"inputs":[],"name":"AlreadyReferredByUser","type":"error"},{"inputs":[],"name":"AlreadySetCode","type":"error"},{"inputs":[],"name":"CodeNotAvailable","type":"error"},{"inputs":[],"name":"InvalidCode","type":"error"},{"inputs":[],"name":"InvalidLevel","type":"error"},{"inputs":[],"name":"LengthMismatch","type":"error"},{"inputs":[],"name":"MissingReferral","type":"error"},{"inputs":[],"name":"MustBeAtLeastBronze","type":"error"},{"inputs":[],"name":"ReciprocalReferral","type":"error"},{"inputs":[],"name":"SelfReferral","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"referrer","type":"address"},{"indexed":false,"internalType":"uint8","name":"_boostLevel","type":"uint8"}],"name":"BoostedReferrer","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address[]","name":"referrer","type":"address[]"},{"indexed":false,"internalType":"uint8[]","name":"_boostLevel","type":"uint8[]"}],"name":"BoostedReferrers","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256[]","name":"_refPointsReq","type":"uint256[]"},{"indexed":false,"internalType":"uint256[]","name":"_selfPointsReq","type":"uint256[]"},{"indexed":false,"internalType":"uint256[]","name":"_refsReq","type":"uint256[]"},{"indexed":false,"internalType":"uint256[]","name":"_multRew","type":"uint256[]"},{"indexed":false,"internalType":"uint256","name":"_hasReferrerBonusMult","type":"uint256"}],"name":"UpdatedLevelData","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_pointsContract","type":"address"}],"name":"UpdatedPointsContract","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_caller","type":"address"},{"indexed":true,"internalType":"address","name":"_user","type":"address"},{"indexed":false,"internalType":"string","name":"_code","type":"string"}],"name":"UpdatedReferralCode","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_add","type":"address"},{"indexed":true,"internalType":"address","name":"_referrer","type":"address"}],"name":"UpdatedReferrer","type":"event"},{"inputs":[],"name":"BONUS_FOR_BEING_REFERRED","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"LEVEL_MULT_REWARD","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"LEVEL_REFS_REQ","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"LEVEL_REF_VOLUME_REQ","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"LEVEL_SELF_VOLUME_REQ","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAINTAINER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"REFERRER","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"REF_BOOST_LEVEL","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"}],"name":"REF_CODE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"REF_CODE_INV","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"REF_COUNT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SUMMIT_POINTS","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addedMaintainer","type":"address"}],"name":"addMaintainer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_referrer","type":"address"},{"internalType":"uint8","name":"_boostLevel","type":"uint8"}],"name":"boostReferrer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"_referrers","type":"address[]"},{"internalType":"uint8[]","name":"_levels","type":"uint8[]"}],"name":"boostReferrers","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint8","name":"_level","type":"uint8"}],"name":"getLevelRequirements","outputs":[{"internalType":"uint256","name":"selfVolume","type":"uint256"},{"internalType":"uint256","name":"refVolume","type":"uint256"},{"internalType":"uint256","name":"refsCount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_add","type":"address"}],"name":"getRefVolumeBonusMultiplier","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_add","type":"address"}],"name":"getReferrer","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_add","type":"address"}],"name":"getReferrerCode","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_add","type":"address"}],"name":"getReferrerLevel","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_add","type":"address"}],"name":"getReferrerLevelWithoutBoost","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_add","type":"address"}],"name":"getRefsCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_add","type":"address"}],"name":"getSelfVolumeMultiplier","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_add","type":"address"}],"name":"getUserNextLevelRequirements","outputs":[{"internalType":"uint256","name":"selfVolume","type":"uint256"},{"internalType":"uint256","name":"refVolume","type":"uint256"},{"internalType":"uint256","name":"refsCount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"governor","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_governor","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"initialized","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"levelCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_add","type":"address"},{"internalType":"string","name":"_code","type":"string"}],"name":"maintainerSetReferralCode","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"removedMaintainer","type":"address"}],"name":"removeMaintainer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_refVolumeReq","type":"uint256[]"},{"internalType":"uint256[]","name":"_selfVolumeReq","type":"uint256[]"},{"internalType":"uint256[]","name":"_refsReq","type":"uint256[]"},{"internalType":"uint256[]","name":"_multReward","type":"uint256[]"},{"internalType":"uint256","name":"_hasReferrerBonusMult","type":"uint256"}],"name":"setLevelData","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_pointsContract","type":"address"}],"name":"setPointsContract","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"_code","type":"string"}],"name":"setReferralCode","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_referrer","type":"address"},{"internalType":"string","name":"_code","type":"string"}],"name":"setReferrer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"}]

6080604052600660025560c8600755600d805460ff191690553480156200002557600080fd5b5062000033600033620002ad565b6200005f7f339759585899103d2ace64958e37e18ccb0504652c81d4a1b8aa80fe2126ab9533620002ad565b60006040518060c00160405280600081526020016000815260200169021e19e0c9bab2400000815260200169054b40b1f852bda00000815260200169152d02c7e14af6800000815260200169d3c21bcecceda1000000815250905060006040518060c001604052806000815260200168056bc75e2d631000008152602001683635c9adc5dea000008152602001686c6b935b8bbd400000815260200169010f0cf064dd59200000815260200169054b40b1f852bda00000815250905060006040518060c0016040528060008152602001600081526020016003815260200160058152602001600a81526020016019815250905060006040518060c001604052806000815260200160c8815260200161019081526020016101f481526020016102bc81526020016105dc815250905060005b600254811015620002a257848160068110620001bc57634e487b7160e01b600052603260045260246000fd5b60200201516003600083815260200190815260200160002081905550838160068110620001f957634e487b7160e01b600052603260045260246000fd5b602002015160046000838152602001908152602001600020819055508281600681106200023657634e487b7160e01b600052603260045260246000fd5b602002015160056000838152602001908152602001600020819055508181600681106200027357634e487b7160e01b600052603260045260246000fd5b60209081029190910151600083815260069092526040909120558062000299816200035d565b91505062000190565b505050505062000385565b620002b98282620002bd565b5050565b6000828152602081815260408083206001600160a01b038516845290915290205460ff16620002b9576000828152602081815260408083206001600160a01b03851684529091529020805460ff19166001179055620003193390565b6001600160a01b0316816001600160a01b0316837f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a45050565b60006000198214156200037e57634e487b7160e01b81526011600452602481fd5b5060010190565b612a4f80620003956000396000f3fe608060405234801561001057600080fd5b50600436106102e95760003560e01c806391d1485411610191578063c4d66de8116100e3578063e0f442d511610097578063fa98b69d11610071578063fa98b69d1461072f578063fb4e6c2d1461074f578063fd0dfb851461076257600080fd5b8063e0f442d5146106e2578063f2fde38b146106f5578063f87422541461070857600080fd5b8063d547741f116100c8578063d547741f146106a9578063d8baf7cf146106bc578063de2ad91a146106cf57600080fd5b8063c4d66de81461066d578063ce0b66391461068057600080fd5b8063a4b0df2511610145578063b8ef9fb31161011f578063b8ef9fb314610611578063ba32aa151461063a578063bc826e641461064d57600080fd5b8063a4b0df25146105cb578063a8654bcb146105de578063affb41a0146105f157600080fd5b8063982b6d4511610176578063982b6d451461056e5780639e8d7cd4146105a3578063a217fddf146105c357600080fd5b806391d148541461052457806395754abb1461055b57600080fd5b80634c10fb251161024a578063651a2b4c116101fe5780637c4e94b5116101d85780637c4e94b5146104de57806386ec3e3d146104fe5780638bb9c5bf1461051157600080fd5b8063651a2b4c146104af5780636b453c1f146104b857806373930f04146104cb57600080fd5b806359ee3c5b1161022f57806359ee3c5b146104485780635a3553c9146104685780635cefd2c61461049c57600080fd5b80634c10fb2514610407578063570f61eb1461041a57600080fd5b8063248a9ca3116102a157806336568abe1161028657806336568abe146103b55780633fe9bba8146103c85780634a9fefc7146103db57600080fd5b8063248a9ca31461037d5780632f2ff15d146103a057600080fd5b8063080472d2116102d2578063080472d21461032d5780630c340a2414610358578063158ef93e1461037057600080fd5b806301e6896b146102ee57806301ffc9a71461030a575b600080fd5b6102f760075481565b6040519081526020015b60405180910390f35b61031d610318366004612528565b610775565b6040519015158152602001610301565b600154610340906001600160a01b031681565b6040516001600160a01b039091168152602001610301565b600d546103409061010090046001600160a01b031681565b600d5461031d9060ff1681565b6102f761038b3660046124ee565b60009081526020819052604090206001015490565b6103b36103ae366004612506565b61080e565b005b6103b36103c3366004612506565b610838565b6103b36103d636600461243e565b6108c9565b6103406103e93660046122ef565b6001600160a01b039081166000908152600860205260409020541690565b6103b3610415366004612355565b610b1a565b61042d6104283660046125d0565b610c09565b60408051938452602084019290925290820152606001610301565b61045b6104563660046122ef565b610c7b565b6040516103019190612835565b610340610476366004612568565b8051602081830181018051600b825292820191909301209152546001600160a01b031681565b6103b36104aa366004612387565b610d15565b6102f760025481565b6103b36104c63660046122ef565b610f43565b6102f76104d93660046122ef565b610f70565b6102f76104ec3660046124ee565b60036020526000908152604090205481565b6103b361050c366004612568565b610fa8565b6103b361051f3660046124ee565b6111a6565b61031d610532366004612506565b6000918252602082815260408084206001600160a01b0393909316845291905290205460ff1690565b6103b36105693660046122ef565b6111b0565b61059161057c3660046122ef565b600a6020526000908152604090205460ff1681565b60405160ff9091168152602001610301565b6102f76105b13660046124ee565b60046020526000908152604090205481565b6102f7600081565b61042d6105d93660046122ef565b611295565b6102f76105ec3660046122ef565b6112fb565b6102f76105ff3660046124ee565b60056020526000908152604090205481565b61034061061f3660046122ef565b6008602052600090815260409020546001600160a01b031681565b6103b3610648366004612309565b611323565b6102f761065b3660046122ef565b60096020526000908152604090205481565b6103b361067b3660046122ef565b6114f0565b6102f761068e3660046122ef565b6001600160a01b031660009081526009602052604090205490565b6103b36106b7366004612506565b6116fe565b6103b36106ca3660046122ef565b611723565b6105916106dd3660046122ef565b61174d565b61045b6106f03660046122ef565b6117d7565b6103b36107033660046122ef565b611892565b6102f77f339759585899103d2ace64958e37e18ccb0504652c81d4a1b8aa80fe2126ab9581565b6102f761073d3660046124ee565b60066020526000908152604090205481565b61059161075d3660046122ef565b6118a8565b6103b3610770366004612309565b611a39565b60007fffffffff0000000000000000000000000000000000000000000000000000000082167f7965db0b00000000000000000000000000000000000000000000000000000000148061080857507f01ffc9a7000000000000000000000000000000000000000000000000000000007fffffffff000000000000000000000000000000000000000000000000000000008316145b92915050565b60008281526020819052604090206001015461082981611d11565b6108338383611d1b565b505050565b6001600160a01b03811633146108bb5760405162461bcd60e51b815260206004820152602f60248201527f416363657373436f6e74726f6c3a2063616e206f6e6c792072656e6f756e636560448201527f20726f6c657320666f722073656c66000000000000000000000000000000000060648201526084015b60405180910390fd5b6108c58282611db9565b5050565b3360009081527fa54247010af6b3693b80aceddfad12e077c5de3571e6243fada502635f0d7d39602052604090205460ff166109585760405162461bcd60e51b815260206004820152602860248201527f4d61696e7461696e61626c653a2043616c6c6572206973206e6f742061206d6160448201526734b73a30b4b732b960c11b60648201526084016108b2565b8351855114158061096b57508251855114155b8061097857508151855114155b156109af576040517fff633a3800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b845160005b81811015610ac7578681815181106109dc57634e487b7160e01b600052603260045260246000fd5b60200260200101516003600083815260200190815260200160002081905550858181518110610a1b57634e487b7160e01b600052603260045260246000fd5b60200260200101516004600083815260200190815260200160002081905550848181518110610a5a57634e487b7160e01b600052603260045260246000fd5b60200260200101516005600083815260200190815260200160002081905550838181518110610a9957634e487b7160e01b600052603260045260246000fd5b6020908102919091018101516000838152600690925260409091205580610abf816129b2565b9150506109b4565b50600281905560078290556040517f562c204f2c2ef9fa88aa881cd4d49bc65849451a4bdb97fad62dc3a5eae6041390610b0a90889088908890889088906127d5565b60405180910390a1505050505050565b3360009081527fa54247010af6b3693b80aceddfad12e077c5de3571e6243fada502635f0d7d39602052604090205460ff16610ba95760405162461bcd60e51b815260206004820152602860248201527f4d61696e7461696e61626c653a2043616c6c6572206973206e6f742061206d6160448201526734b73a30b4b732b960c11b60648201526084016108b2565b6001600160a01b0382166000818152600a6020908152604091829020805460ff191660ff861690811790915591519182527fc4f4125174e3f54bd4ffb0f92384ddca0b2dc931f194e9f5efb1a1b88443fb32910160405180910390a25050565b60008060006002548460ff1610610c4c576040517fd1459f7900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b50505060ff16600090815260046020908152604080832054600383528184205460059093529220549192909190565b600c6020526000908152604090208054610c9490612977565b80601f0160208091040260200160405190810160405280929190818152602001828054610cc090612977565b8015610d0d5780601f10610ce257610100808354040283529160200191610d0d565b820191906000526020600020905b815481529060010190602001808311610cf057829003601f168201915b505050505081565b3360009081527fa54247010af6b3693b80aceddfad12e077c5de3571e6243fada502635f0d7d39602052604090205460ff16610da45760405162461bcd60e51b815260206004820152602860248201527f4d61696e7461696e61626c653a2043616c6c6572206973206e6f742061206d6160448201526734b73a30b4b732b960c11b60648201526084016108b2565b8051825114610ddf576040517fff633a3800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60005b8251811015610f0557818181518110610e0b57634e487b7160e01b600052603260045260246000fd5b602002602001015160ff16600a6000858481518110610e3a57634e487b7160e01b600052603260045260246000fd5b6020908102919091018101516001600160a01b031682528101919091526040016000205460ff1614610ef357818181518110610e8657634e487b7160e01b600052603260045260246000fd5b6020026020010151600a6000858481518110610eb257634e487b7160e01b600052603260045260246000fd5b60200260200101516001600160a01b03166001600160a01b0316815260200190815260200160002060006101000a81548160ff021916908360ff1602179055505b80610efd816129b2565b915050610de2565b507fa8ebc437633f921c0b85258fb33b74715331194b7fdc1627091b38ce0ea7afd48282604051610f3792919061275c565b60405180910390a15050565b610f6d7f339759585899103d2ace64958e37e18ccb0504652c81d4a1b8aa80fe2126ab958261080e565b50565b6001600160a01b03818116600090815260086020526040812054909116610f98576000610f9c565b6007545b610808906127106128bd565b600381511080610fb95750600f8151115b15610ff0576040517f6606a46200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b336000908152600c60205260409020805461100a90612977565b159050611043576040517fad51e20300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61104c3361174d565b60ff1661106c576040516338b729c360e11b815260040160405180910390fd5b60006001600160a01b0316600b826040516110879190612624565b908152604051908190036020019020546001600160a01b0316146110be5760405163096a1aab60e21b815260040160405180910390fd5b336000908152600c60205260408082209051600b916110dc91612640565b908152604080516020928190038301902080546001600160a01b0319166001600160a01b039490941693909317909255336000908152600c8252919091208251611128928401906120eb565b5033600b8260405161113a9190612624565b90815260405190819003602001812080546001600160a01b03939093166001600160a01b031990931692909217909155339081907f627a1b03408ad803620768c059b68f695d3740108ac22fd6cdab3c887b162cbe9061119b908590612835565b60405180910390a350565b610f6d8133610838565b3360009081527fa54247010af6b3693b80aceddfad12e077c5de3571e6243fada502635f0d7d39602052604090205460ff1661123f5760405162461bcd60e51b815260206004820152602860248201527f4d61696e7461696e61626c653a2043616c6c6572206973206e6f742061206d6160448201526734b73a30b4b732b960c11b60648201526084016108b2565b6040516001600160a01b038216907fc44f7b4fe9d29c078d207864fd556e8b68e1759e3ac4ee9cece99d2ec03c27c690600090a2600180546001600160a01b0319166001600160a01b0392909216919091179055565b6000806000806112a4856118a8565b6112af9060016128d5565b9050600060016002546112c29190612919565b8260ff1610156112d257816112e1565b60016002546112e19190612919565b90506112ec81610c09565b94509450945050509193909250565b60006006600061130a8461174d565b60ff168152602001908152602001600020549050919050565b3360009081527fa54247010af6b3693b80aceddfad12e077c5de3571e6243fada502635f0d7d39602052604090205460ff166113b25760405162461bcd60e51b815260206004820152602860248201527f4d61696e7461696e61626c653a2043616c6c6572206973206e6f742061206d6160448201526734b73a30b4b732b960c11b60648201526084016108b2565b60006001600160a01b0316600b826040516113cd9190612624565b908152604051908190036020019020546001600160a01b0316146114045760405163096a1aab60e21b815260040160405180910390fd5b6001600160a01b0382166000908152600c60205260408082209051600b9161142b91612640565b908152604080516020928190038301902080546001600160a01b0319166001600160a01b039485161790559184166000908152600c8252919091208251611474928401906120eb565b5081600b826040516114869190612624565b90815260405190819003602001812080546001600160a01b039384166001600160a01b03199091161790559083169033907f627a1b03408ad803620768c059b68f695d3740108ac22fd6cdab3c887b162cbe906114e4908590612835565b60405180910390a35050565b3360009081527fa54247010af6b3693b80aceddfad12e077c5de3571e6243fada502635f0d7d39602052604090205460ff1661157f5760405162461bcd60e51b815260206004820152602860248201527f4d61696e7461696e61626c653a2043616c6c6572206973206e6f742061206d6160448201526734b73a30b4b732b960c11b60648201526084016108b2565b600d5460ff16156115bc576040517f0dc149f000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600d805460ff19166001179055604080517f4e606c470000000000000000000000000000000000000000000000000000000081529051734300000000000000000000000000000000000002918291634e606c479160048082019260009290919082900301818387803b15801561163157600080fd5b505af1158015611645573d6000803e3d6000fd5b50506040517feb8646980000000000000000000000000000000000000000000000000000000081526001600160a01b0385811660048301528416925063eb8646989150602401600060405180830381600087803b1580156116a557600080fd5b505af11580156116b9573d6000803e3d6000fd5b5050600d80546001600160a01b03909516610100027fffffffffffffffffffffff0000000000000000000000000000000000000000ff90951694909417909355505050565b60008281526020819052604090206001015461171981611d11565b6108338383611db9565b610f6d7f339759585899103d2ace64958e37e18ccb0504652c81d4a1b8aa80fe2126ab95826116fe565b6001600160a01b0381166000908152600a602052604081205460ff16156117ce57600160025461177d9190612919565b6001600160a01b0383166000908152600a602052604090205460ff16116117bf576001600160a01b0382166000908152600a602052604090205460ff16610808565b60016002546108089190612919565b610808826118a8565b6001600160a01b038082166000908152600860209081526040808320549093168252600c90522080546060919061180d90612977565b80601f016020809104026020016040519081016040528092919081815260200182805461183990612977565b80156118865780601f1061185b57610100808354040283529160200191611886565b820191906000526020600020905b81548152906001019060200180831161186957829003601f168201915b50505050509050919050565b61189d60008261080e565b610f6d600033610838565b6001546000906001600160a01b03166118c357506000919050565b6001546040517f8ba17f860000000000000000000000000000000000000000000000000000000081526001600160a01b0384811660048301526000928392911690638ba17f869060240160606040518083038186803b15801561192557600080fd5b505afa158015611939573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061195d91906125a3565b509150915060005b6002548160ff161015611a2d57600460006119818360016128d5565b60ff168152602001908152602001600020548310156119a257949350505050565b600360006119b18360016128d5565b60ff168152602001908152602001600020548210156119d257949350505050565b600560006119e18360016128d5565b60ff16815260208082019290925260409081016000908120546001600160a01b0389168252600990935220541015611a1b57949350505050565b80611a25816129cd565b915050611965565b50506002549392505050565b60006001600160a01b03831615611a505782611a7a565b600b82604051611a609190612624565b908152604051908190036020019020546001600160a01b03165b90506001600160a01b038116611abc576040517fcd2d66aa00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6001600160a01b038116331415611aff576040517f55e8f70e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b336000908152600860205260409020546001600160a01b0382811691161415611b54576040517ffe1ffa9500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6001600160a01b0381811660009081526008602052604090205416331415611b8f5760405163b6ea0b0160e01b815260040160405180910390fd5b6001600160a01b03818116600090815260086020526040808220548316825290205416331415611bd25760405163b6ea0b0160e01b815260040160405180910390fd5b611bdb8161174d565b60ff16611bfb576040516338b729c360e11b815260040160405180910390fd5b336000908152600860205260409020546001600160a01b031615801590611c485750336000908152600860209081526040808320546001600160a01b031683526009909152902054600111155b15611c8957336000908152600860209081526040808320546001600160a01b0316835260099091528120805460019290611c83908490612919565b90915550505b33600090815260086020908152604080832080546001600160a01b0319166001600160a01b038616908117909155835260099091528120805460019290611cd19084906128bd565b90915550506040516001600160a01b0382169033907f82ca166ebb8d22637409e8fcca994e7e1b757a1e28f0ee91f4defe3cff1e919390600090a3505050565b610f6d8133611e38565b6000828152602081815260408083206001600160a01b038516845290915290205460ff166108c5576000828152602081815260408083206001600160a01b03851684529091529020805460ff19166001179055611d753390565b6001600160a01b0316816001600160a01b0316837f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a45050565b6000828152602081815260408083206001600160a01b038516845290915290205460ff16156108c5576000828152602081815260408083206001600160a01b0385168085529252808320805460ff1916905551339285917ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b9190a45050565b6000828152602081815260408083206001600160a01b038516845290915290205460ff166108c557611e6981611eab565b611e74836020611ebd565b604051602001611e859291906126db565b60408051601f198184030181529082905262461bcd60e51b82526108b291600401612835565b60606108086001600160a01b03831660145b60606000611ecc8360026128fa565b611ed79060026128bd565b67ffffffffffffffff811115611efd57634e487b7160e01b600052604160045260246000fd5b6040519080825280601f01601f191660200182016040528015611f27576020820181803683370190505b5090507f300000000000000000000000000000000000000000000000000000000000000081600081518110611f6c57634e487b7160e01b600052603260045260246000fd5b60200101906001600160f81b031916908160001a9053507f780000000000000000000000000000000000000000000000000000000000000081600181518110611fc557634e487b7160e01b600052603260045260246000fd5b60200101906001600160f81b031916908160001a9053506000611fe98460026128fa565b611ff49060016128bd565b90505b6001811115612095577f303132333435363738396162636465660000000000000000000000000000000085600f166010811061204357634e487b7160e01b600052603260045260246000fd5b1a60f81b82828151811061206757634e487b7160e01b600052603260045260246000fd5b60200101906001600160f81b031916908160001a90535060049490941c9361208e81612960565b9050611ff7565b5083156120e45760405162461bcd60e51b815260206004820181905260248201527f537472696e67733a20686578206c656e67746820696e73756666696369656e7460448201526064016108b2565b9392505050565b8280546120f790612977565b90600052602060002090601f016020900481019282612119576000855561215f565b82601f1061213257805160ff191683800117855561215f565b8280016001018555821561215f579182015b8281111561215f578251825591602001919060010190612144565b5061216b92915061216f565b5090565b5b8082111561216b5760008155600101612170565b80356001600160a01b038116811461219b57600080fd5b919050565b600082601f8301126121b0578081fd5b813560206121c56121c083612899565b612868565b80838252828201915082860187848660051b89010111156121e4578586fd5b855b85811015612202578135845292840192908401906001016121e6565b5090979650505050505050565b600082601f83011261221f578081fd5b8135602061222f6121c083612899565b80838252828201915082860187848660051b890101111561224e578586fd5b855b8581101561220257612261826122de565b84529284019290840190600101612250565b600082601f830112612283578081fd5b813567ffffffffffffffff81111561229d5761229d612a03565b6122b0601f8201601f1916602001612868565b8181528460208386010111156122c4578283fd5b816020850160208301379081016020019190915292915050565b803560ff8116811461219b57600080fd5b600060208284031215612300578081fd5b6120e482612184565b6000806040838503121561231b578081fd5b61232483612184565b9150602083013567ffffffffffffffff81111561233f578182fd5b61234b85828601612273565b9150509250929050565b60008060408385031215612367578182fd5b61237083612184565b915061237e602084016122de565b90509250929050565b60008060408385031215612399578182fd5b823567ffffffffffffffff808211156123b0578384fd5b818501915085601f8301126123c3578384fd5b813560206123d36121c083612899565b8083825282820191508286018a848660051b89010111156123f2578889fd5b8896505b8487101561241b5761240781612184565b8352600196909601959183019183016123f6565b5096505086013592505080821115612431578283fd5b5061234b8582860161220f565b600080600080600060a08688031215612455578081fd5b853567ffffffffffffffff8082111561246c578283fd5b61247889838a016121a0565b9650602088013591508082111561248d578283fd5b61249989838a016121a0565b955060408801359150808211156124ae578283fd5b6124ba89838a016121a0565b945060608801359150808211156124cf578283fd5b506124dc888289016121a0565b95989497509295608001359392505050565b6000602082840312156124ff578081fd5b5035919050565b60008060408385031215612518578182fd5b8235915061237e60208401612184565b600060208284031215612539578081fd5b81357fffffffff00000000000000000000000000000000000000000000000000000000811681146120e4578182fd5b600060208284031215612579578081fd5b813567ffffffffffffffff81111561258f578182fd5b61259b84828501612273565b949350505050565b6000806000606084860312156125b7578081fd5b8351925060208401519150604084015190509250925092565b6000602082840312156125e1578081fd5b6120e4826122de565b6000815180845260208085019450808401835b83811015612619578151875295820195908201906001016125fd565b509495945050505050565b60008251612636818460208701612930565b9190910192915050565b600080835482600182811c91508083168061265c57607f831692505b602080841082141561267c57634e487b7160e01b87526022600452602487fd5b81801561269057600181146126a1576126cd565b60ff198616895284890196506126cd565b60008a815260209020885b868110156126c55781548b8201529085019083016126ac565b505084890196505b509498975050505050505050565b7f416363657373436f6e74726f6c3a206163636f756e7420000000000000000000815260008351612713816017850160208801612930565b7f206973206d697373696e6720726f6c65200000000000000000000000000000006017918401918201528351612750816028840160208801612930565b01602801949350505050565b604080825283519082018190526000906020906060840190828701845b8281101561279e5781516001600160a01b031684529284019290840190600101612779565b50505083810382850152845180825285830191830190845b8181101561220257835160ff16835292840192918401916001016127b6565b60a0815260006127e860a08301886125ea565b82810360208401526127fa81886125ea565b9050828103604084015261280e81876125ea565b9050828103606084015261282281866125ea565b9150508260808301529695505050505050565b6020815260008251806020840152612854816040850160208701612930565b601f01601f19169190910160400192915050565b604051601f8201601f1916810167ffffffffffffffff8111828210171561289157612891612a03565b604052919050565b600067ffffffffffffffff8211156128b3576128b3612a03565b5060051b60200190565b600082198211156128d0576128d06129ed565b500190565b600060ff821660ff84168060ff038211156128f2576128f26129ed565b019392505050565b6000816000190483118215151615612914576129146129ed565b500290565b60008282101561292b5761292b6129ed565b500390565b60005b8381101561294b578181015183820152602001612933565b8381111561295a576000848401525b50505050565b60008161296f5761296f6129ed565b506000190190565b600181811c9082168061298b57607f821691505b602082108114156129ac57634e487b7160e01b600052602260045260246000fd5b50919050565b60006000198214156129c6576129c66129ed565b5060010190565b600060ff821660ff8114156129e4576129e46129ed565b60010192915050565b634e487b7160e01b600052601160045260246000fd5b634e487b7160e01b600052604160045260246000fdfea264697066735822122081cb55390721778e5ebde06ffdba4100e63be4df1d04c411eb635601e1b13d2164736f6c63430008040033

Deployed Bytecode

0x608060405234801561001057600080fd5b50600436106102e95760003560e01c806391d1485411610191578063c4d66de8116100e3578063e0f442d511610097578063fa98b69d11610071578063fa98b69d1461072f578063fb4e6c2d1461074f578063fd0dfb851461076257600080fd5b8063e0f442d5146106e2578063f2fde38b146106f5578063f87422541461070857600080fd5b8063d547741f116100c8578063d547741f146106a9578063d8baf7cf146106bc578063de2ad91a146106cf57600080fd5b8063c4d66de81461066d578063ce0b66391461068057600080fd5b8063a4b0df2511610145578063b8ef9fb31161011f578063b8ef9fb314610611578063ba32aa151461063a578063bc826e641461064d57600080fd5b8063a4b0df25146105cb578063a8654bcb146105de578063affb41a0146105f157600080fd5b8063982b6d4511610176578063982b6d451461056e5780639e8d7cd4146105a3578063a217fddf146105c357600080fd5b806391d148541461052457806395754abb1461055b57600080fd5b80634c10fb251161024a578063651a2b4c116101fe5780637c4e94b5116101d85780637c4e94b5146104de57806386ec3e3d146104fe5780638bb9c5bf1461051157600080fd5b8063651a2b4c146104af5780636b453c1f146104b857806373930f04146104cb57600080fd5b806359ee3c5b1161022f57806359ee3c5b146104485780635a3553c9146104685780635cefd2c61461049c57600080fd5b80634c10fb2514610407578063570f61eb1461041a57600080fd5b8063248a9ca3116102a157806336568abe1161028657806336568abe146103b55780633fe9bba8146103c85780634a9fefc7146103db57600080fd5b8063248a9ca31461037d5780632f2ff15d146103a057600080fd5b8063080472d2116102d2578063080472d21461032d5780630c340a2414610358578063158ef93e1461037057600080fd5b806301e6896b146102ee57806301ffc9a71461030a575b600080fd5b6102f760075481565b6040519081526020015b60405180910390f35b61031d610318366004612528565b610775565b6040519015158152602001610301565b600154610340906001600160a01b031681565b6040516001600160a01b039091168152602001610301565b600d546103409061010090046001600160a01b031681565b600d5461031d9060ff1681565b6102f761038b3660046124ee565b60009081526020819052604090206001015490565b6103b36103ae366004612506565b61080e565b005b6103b36103c3366004612506565b610838565b6103b36103d636600461243e565b6108c9565b6103406103e93660046122ef565b6001600160a01b039081166000908152600860205260409020541690565b6103b3610415366004612355565b610b1a565b61042d6104283660046125d0565b610c09565b60408051938452602084019290925290820152606001610301565b61045b6104563660046122ef565b610c7b565b6040516103019190612835565b610340610476366004612568565b8051602081830181018051600b825292820191909301209152546001600160a01b031681565b6103b36104aa366004612387565b610d15565b6102f760025481565b6103b36104c63660046122ef565b610f43565b6102f76104d93660046122ef565b610f70565b6102f76104ec3660046124ee565b60036020526000908152604090205481565b6103b361050c366004612568565b610fa8565b6103b361051f3660046124ee565b6111a6565b61031d610532366004612506565b6000918252602082815260408084206001600160a01b0393909316845291905290205460ff1690565b6103b36105693660046122ef565b6111b0565b61059161057c3660046122ef565b600a6020526000908152604090205460ff1681565b60405160ff9091168152602001610301565b6102f76105b13660046124ee565b60046020526000908152604090205481565b6102f7600081565b61042d6105d93660046122ef565b611295565b6102f76105ec3660046122ef565b6112fb565b6102f76105ff3660046124ee565b60056020526000908152604090205481565b61034061061f3660046122ef565b6008602052600090815260409020546001600160a01b031681565b6103b3610648366004612309565b611323565b6102f761065b3660046122ef565b60096020526000908152604090205481565b6103b361067b3660046122ef565b6114f0565b6102f761068e3660046122ef565b6001600160a01b031660009081526009602052604090205490565b6103b36106b7366004612506565b6116fe565b6103b36106ca3660046122ef565b611723565b6105916106dd3660046122ef565b61174d565b61045b6106f03660046122ef565b6117d7565b6103b36107033660046122ef565b611892565b6102f77f339759585899103d2ace64958e37e18ccb0504652c81d4a1b8aa80fe2126ab9581565b6102f761073d3660046124ee565b60066020526000908152604090205481565b61059161075d3660046122ef565b6118a8565b6103b3610770366004612309565b611a39565b60007fffffffff0000000000000000000000000000000000000000000000000000000082167f7965db0b00000000000000000000000000000000000000000000000000000000148061080857507f01ffc9a7000000000000000000000000000000000000000000000000000000007fffffffff000000000000000000000000000000000000000000000000000000008316145b92915050565b60008281526020819052604090206001015461082981611d11565b6108338383611d1b565b505050565b6001600160a01b03811633146108bb5760405162461bcd60e51b815260206004820152602f60248201527f416363657373436f6e74726f6c3a2063616e206f6e6c792072656e6f756e636560448201527f20726f6c657320666f722073656c66000000000000000000000000000000000060648201526084015b60405180910390fd5b6108c58282611db9565b5050565b3360009081527fa54247010af6b3693b80aceddfad12e077c5de3571e6243fada502635f0d7d39602052604090205460ff166109585760405162461bcd60e51b815260206004820152602860248201527f4d61696e7461696e61626c653a2043616c6c6572206973206e6f742061206d6160448201526734b73a30b4b732b960c11b60648201526084016108b2565b8351855114158061096b57508251855114155b8061097857508151855114155b156109af576040517fff633a3800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b845160005b81811015610ac7578681815181106109dc57634e487b7160e01b600052603260045260246000fd5b60200260200101516003600083815260200190815260200160002081905550858181518110610a1b57634e487b7160e01b600052603260045260246000fd5b60200260200101516004600083815260200190815260200160002081905550848181518110610a5a57634e487b7160e01b600052603260045260246000fd5b60200260200101516005600083815260200190815260200160002081905550838181518110610a9957634e487b7160e01b600052603260045260246000fd5b6020908102919091018101516000838152600690925260409091205580610abf816129b2565b9150506109b4565b50600281905560078290556040517f562c204f2c2ef9fa88aa881cd4d49bc65849451a4bdb97fad62dc3a5eae6041390610b0a90889088908890889088906127d5565b60405180910390a1505050505050565b3360009081527fa54247010af6b3693b80aceddfad12e077c5de3571e6243fada502635f0d7d39602052604090205460ff16610ba95760405162461bcd60e51b815260206004820152602860248201527f4d61696e7461696e61626c653a2043616c6c6572206973206e6f742061206d6160448201526734b73a30b4b732b960c11b60648201526084016108b2565b6001600160a01b0382166000818152600a6020908152604091829020805460ff191660ff861690811790915591519182527fc4f4125174e3f54bd4ffb0f92384ddca0b2dc931f194e9f5efb1a1b88443fb32910160405180910390a25050565b60008060006002548460ff1610610c4c576040517fd1459f7900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b50505060ff16600090815260046020908152604080832054600383528184205460059093529220549192909190565b600c6020526000908152604090208054610c9490612977565b80601f0160208091040260200160405190810160405280929190818152602001828054610cc090612977565b8015610d0d5780601f10610ce257610100808354040283529160200191610d0d565b820191906000526020600020905b815481529060010190602001808311610cf057829003601f168201915b505050505081565b3360009081527fa54247010af6b3693b80aceddfad12e077c5de3571e6243fada502635f0d7d39602052604090205460ff16610da45760405162461bcd60e51b815260206004820152602860248201527f4d61696e7461696e61626c653a2043616c6c6572206973206e6f742061206d6160448201526734b73a30b4b732b960c11b60648201526084016108b2565b8051825114610ddf576040517fff633a3800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60005b8251811015610f0557818181518110610e0b57634e487b7160e01b600052603260045260246000fd5b602002602001015160ff16600a6000858481518110610e3a57634e487b7160e01b600052603260045260246000fd5b6020908102919091018101516001600160a01b031682528101919091526040016000205460ff1614610ef357818181518110610e8657634e487b7160e01b600052603260045260246000fd5b6020026020010151600a6000858481518110610eb257634e487b7160e01b600052603260045260246000fd5b60200260200101516001600160a01b03166001600160a01b0316815260200190815260200160002060006101000a81548160ff021916908360ff1602179055505b80610efd816129b2565b915050610de2565b507fa8ebc437633f921c0b85258fb33b74715331194b7fdc1627091b38ce0ea7afd48282604051610f3792919061275c565b60405180910390a15050565b610f6d7f339759585899103d2ace64958e37e18ccb0504652c81d4a1b8aa80fe2126ab958261080e565b50565b6001600160a01b03818116600090815260086020526040812054909116610f98576000610f9c565b6007545b610808906127106128bd565b600381511080610fb95750600f8151115b15610ff0576040517f6606a46200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b336000908152600c60205260409020805461100a90612977565b159050611043576040517fad51e20300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61104c3361174d565b60ff1661106c576040516338b729c360e11b815260040160405180910390fd5b60006001600160a01b0316600b826040516110879190612624565b908152604051908190036020019020546001600160a01b0316146110be5760405163096a1aab60e21b815260040160405180910390fd5b336000908152600c60205260408082209051600b916110dc91612640565b908152604080516020928190038301902080546001600160a01b0319166001600160a01b039490941693909317909255336000908152600c8252919091208251611128928401906120eb565b5033600b8260405161113a9190612624565b90815260405190819003602001812080546001600160a01b03939093166001600160a01b031990931692909217909155339081907f627a1b03408ad803620768c059b68f695d3740108ac22fd6cdab3c887b162cbe9061119b908590612835565b60405180910390a350565b610f6d8133610838565b3360009081527fa54247010af6b3693b80aceddfad12e077c5de3571e6243fada502635f0d7d39602052604090205460ff1661123f5760405162461bcd60e51b815260206004820152602860248201527f4d61696e7461696e61626c653a2043616c6c6572206973206e6f742061206d6160448201526734b73a30b4b732b960c11b60648201526084016108b2565b6040516001600160a01b038216907fc44f7b4fe9d29c078d207864fd556e8b68e1759e3ac4ee9cece99d2ec03c27c690600090a2600180546001600160a01b0319166001600160a01b0392909216919091179055565b6000806000806112a4856118a8565b6112af9060016128d5565b9050600060016002546112c29190612919565b8260ff1610156112d257816112e1565b60016002546112e19190612919565b90506112ec81610c09565b94509450945050509193909250565b60006006600061130a8461174d565b60ff168152602001908152602001600020549050919050565b3360009081527fa54247010af6b3693b80aceddfad12e077c5de3571e6243fada502635f0d7d39602052604090205460ff166113b25760405162461bcd60e51b815260206004820152602860248201527f4d61696e7461696e61626c653a2043616c6c6572206973206e6f742061206d6160448201526734b73a30b4b732b960c11b60648201526084016108b2565b60006001600160a01b0316600b826040516113cd9190612624565b908152604051908190036020019020546001600160a01b0316146114045760405163096a1aab60e21b815260040160405180910390fd5b6001600160a01b0382166000908152600c60205260408082209051600b9161142b91612640565b908152604080516020928190038301902080546001600160a01b0319166001600160a01b039485161790559184166000908152600c8252919091208251611474928401906120eb565b5081600b826040516114869190612624565b90815260405190819003602001812080546001600160a01b039384166001600160a01b03199091161790559083169033907f627a1b03408ad803620768c059b68f695d3740108ac22fd6cdab3c887b162cbe906114e4908590612835565b60405180910390a35050565b3360009081527fa54247010af6b3693b80aceddfad12e077c5de3571e6243fada502635f0d7d39602052604090205460ff1661157f5760405162461bcd60e51b815260206004820152602860248201527f4d61696e7461696e61626c653a2043616c6c6572206973206e6f742061206d6160448201526734b73a30b4b732b960c11b60648201526084016108b2565b600d5460ff16156115bc576040517f0dc149f000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600d805460ff19166001179055604080517f4e606c470000000000000000000000000000000000000000000000000000000081529051734300000000000000000000000000000000000002918291634e606c479160048082019260009290919082900301818387803b15801561163157600080fd5b505af1158015611645573d6000803e3d6000fd5b50506040517feb8646980000000000000000000000000000000000000000000000000000000081526001600160a01b0385811660048301528416925063eb8646989150602401600060405180830381600087803b1580156116a557600080fd5b505af11580156116b9573d6000803e3d6000fd5b5050600d80546001600160a01b03909516610100027fffffffffffffffffffffff0000000000000000000000000000000000000000ff90951694909417909355505050565b60008281526020819052604090206001015461171981611d11565b6108338383611db9565b610f6d7f339759585899103d2ace64958e37e18ccb0504652c81d4a1b8aa80fe2126ab95826116fe565b6001600160a01b0381166000908152600a602052604081205460ff16156117ce57600160025461177d9190612919565b6001600160a01b0383166000908152600a602052604090205460ff16116117bf576001600160a01b0382166000908152600a602052604090205460ff16610808565b60016002546108089190612919565b610808826118a8565b6001600160a01b038082166000908152600860209081526040808320549093168252600c90522080546060919061180d90612977565b80601f016020809104026020016040519081016040528092919081815260200182805461183990612977565b80156118865780601f1061185b57610100808354040283529160200191611886565b820191906000526020600020905b81548152906001019060200180831161186957829003601f168201915b50505050509050919050565b61189d60008261080e565b610f6d600033610838565b6001546000906001600160a01b03166118c357506000919050565b6001546040517f8ba17f860000000000000000000000000000000000000000000000000000000081526001600160a01b0384811660048301526000928392911690638ba17f869060240160606040518083038186803b15801561192557600080fd5b505afa158015611939573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061195d91906125a3565b509150915060005b6002548160ff161015611a2d57600460006119818360016128d5565b60ff168152602001908152602001600020548310156119a257949350505050565b600360006119b18360016128d5565b60ff168152602001908152602001600020548210156119d257949350505050565b600560006119e18360016128d5565b60ff16815260208082019290925260409081016000908120546001600160a01b0389168252600990935220541015611a1b57949350505050565b80611a25816129cd565b915050611965565b50506002549392505050565b60006001600160a01b03831615611a505782611a7a565b600b82604051611a609190612624565b908152604051908190036020019020546001600160a01b03165b90506001600160a01b038116611abc576040517fcd2d66aa00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6001600160a01b038116331415611aff576040517f55e8f70e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b336000908152600860205260409020546001600160a01b0382811691161415611b54576040517ffe1ffa9500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6001600160a01b0381811660009081526008602052604090205416331415611b8f5760405163b6ea0b0160e01b815260040160405180910390fd5b6001600160a01b03818116600090815260086020526040808220548316825290205416331415611bd25760405163b6ea0b0160e01b815260040160405180910390fd5b611bdb8161174d565b60ff16611bfb576040516338b729c360e11b815260040160405180910390fd5b336000908152600860205260409020546001600160a01b031615801590611c485750336000908152600860209081526040808320546001600160a01b031683526009909152902054600111155b15611c8957336000908152600860209081526040808320546001600160a01b0316835260099091528120805460019290611c83908490612919565b90915550505b33600090815260086020908152604080832080546001600160a01b0319166001600160a01b038616908117909155835260099091528120805460019290611cd19084906128bd565b90915550506040516001600160a01b0382169033907f82ca166ebb8d22637409e8fcca994e7e1b757a1e28f0ee91f4defe3cff1e919390600090a3505050565b610f6d8133611e38565b6000828152602081815260408083206001600160a01b038516845290915290205460ff166108c5576000828152602081815260408083206001600160a01b03851684529091529020805460ff19166001179055611d753390565b6001600160a01b0316816001600160a01b0316837f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a45050565b6000828152602081815260408083206001600160a01b038516845290915290205460ff16156108c5576000828152602081815260408083206001600160a01b0385168085529252808320805460ff1916905551339285917ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b9190a45050565b6000828152602081815260408083206001600160a01b038516845290915290205460ff166108c557611e6981611eab565b611e74836020611ebd565b604051602001611e859291906126db565b60408051601f198184030181529082905262461bcd60e51b82526108b291600401612835565b60606108086001600160a01b03831660145b60606000611ecc8360026128fa565b611ed79060026128bd565b67ffffffffffffffff811115611efd57634e487b7160e01b600052604160045260246000fd5b6040519080825280601f01601f191660200182016040528015611f27576020820181803683370190505b5090507f300000000000000000000000000000000000000000000000000000000000000081600081518110611f6c57634e487b7160e01b600052603260045260246000fd5b60200101906001600160f81b031916908160001a9053507f780000000000000000000000000000000000000000000000000000000000000081600181518110611fc557634e487b7160e01b600052603260045260246000fd5b60200101906001600160f81b031916908160001a9053506000611fe98460026128fa565b611ff49060016128bd565b90505b6001811115612095577f303132333435363738396162636465660000000000000000000000000000000085600f166010811061204357634e487b7160e01b600052603260045260246000fd5b1a60f81b82828151811061206757634e487b7160e01b600052603260045260246000fd5b60200101906001600160f81b031916908160001a90535060049490941c9361208e81612960565b9050611ff7565b5083156120e45760405162461bcd60e51b815260206004820181905260248201527f537472696e67733a20686578206c656e67746820696e73756666696369656e7460448201526064016108b2565b9392505050565b8280546120f790612977565b90600052602060002090601f016020900481019282612119576000855561215f565b82601f1061213257805160ff191683800117855561215f565b8280016001018555821561215f579182015b8281111561215f578251825591602001919060010190612144565b5061216b92915061216f565b5090565b5b8082111561216b5760008155600101612170565b80356001600160a01b038116811461219b57600080fd5b919050565b600082601f8301126121b0578081fd5b813560206121c56121c083612899565b612868565b80838252828201915082860187848660051b89010111156121e4578586fd5b855b85811015612202578135845292840192908401906001016121e6565b5090979650505050505050565b600082601f83011261221f578081fd5b8135602061222f6121c083612899565b80838252828201915082860187848660051b890101111561224e578586fd5b855b8581101561220257612261826122de565b84529284019290840190600101612250565b600082601f830112612283578081fd5b813567ffffffffffffffff81111561229d5761229d612a03565b6122b0601f8201601f1916602001612868565b8181528460208386010111156122c4578283fd5b816020850160208301379081016020019190915292915050565b803560ff8116811461219b57600080fd5b600060208284031215612300578081fd5b6120e482612184565b6000806040838503121561231b578081fd5b61232483612184565b9150602083013567ffffffffffffffff81111561233f578182fd5b61234b85828601612273565b9150509250929050565b60008060408385031215612367578182fd5b61237083612184565b915061237e602084016122de565b90509250929050565b60008060408385031215612399578182fd5b823567ffffffffffffffff808211156123b0578384fd5b818501915085601f8301126123c3578384fd5b813560206123d36121c083612899565b8083825282820191508286018a848660051b89010111156123f2578889fd5b8896505b8487101561241b5761240781612184565b8352600196909601959183019183016123f6565b5096505086013592505080821115612431578283fd5b5061234b8582860161220f565b600080600080600060a08688031215612455578081fd5b853567ffffffffffffffff8082111561246c578283fd5b61247889838a016121a0565b9650602088013591508082111561248d578283fd5b61249989838a016121a0565b955060408801359150808211156124ae578283fd5b6124ba89838a016121a0565b945060608801359150808211156124cf578283fd5b506124dc888289016121a0565b95989497509295608001359392505050565b6000602082840312156124ff578081fd5b5035919050565b60008060408385031215612518578182fd5b8235915061237e60208401612184565b600060208284031215612539578081fd5b81357fffffffff00000000000000000000000000000000000000000000000000000000811681146120e4578182fd5b600060208284031215612579578081fd5b813567ffffffffffffffff81111561258f578182fd5b61259b84828501612273565b949350505050565b6000806000606084860312156125b7578081fd5b8351925060208401519150604084015190509250925092565b6000602082840312156125e1578081fd5b6120e4826122de565b6000815180845260208085019450808401835b83811015612619578151875295820195908201906001016125fd565b509495945050505050565b60008251612636818460208701612930565b9190910192915050565b600080835482600182811c91508083168061265c57607f831692505b602080841082141561267c57634e487b7160e01b87526022600452602487fd5b81801561269057600181146126a1576126cd565b60ff198616895284890196506126cd565b60008a815260209020885b868110156126c55781548b8201529085019083016126ac565b505084890196505b509498975050505050505050565b7f416363657373436f6e74726f6c3a206163636f756e7420000000000000000000815260008351612713816017850160208801612930565b7f206973206d697373696e6720726f6c65200000000000000000000000000000006017918401918201528351612750816028840160208801612930565b01602801949350505050565b604080825283519082018190526000906020906060840190828701845b8281101561279e5781516001600160a01b031684529284019290840190600101612779565b50505083810382850152845180825285830191830190845b8181101561220257835160ff16835292840192918401916001016127b6565b60a0815260006127e860a08301886125ea565b82810360208401526127fa81886125ea565b9050828103604084015261280e81876125ea565b9050828103606084015261282281866125ea565b9150508260808301529695505050505050565b6020815260008251806020840152612854816040850160208701612930565b601f01601f19169190910160400192915050565b604051601f8201601f1916810167ffffffffffffffff8111828210171561289157612891612a03565b604052919050565b600067ffffffffffffffff8211156128b3576128b3612a03565b5060051b60200190565b600082198211156128d0576128d06129ed565b500190565b600060ff821660ff84168060ff038211156128f2576128f26129ed565b019392505050565b6000816000190483118215151615612914576129146129ed565b500290565b60008282101561292b5761292b6129ed565b500390565b60005b8381101561294b578181015183820152602001612933565b8381111561295a576000848401525b50505050565b60008161296f5761296f6129ed565b506000190190565b600181811c9082168061298b57607f821691505b602082108114156129ac57634e487b7160e01b600052602260045260246000fd5b50919050565b60006000198214156129c6576129c66129ed565b5060010190565b600060ff821660ff8114156129e4576129e46129ed565b60010192915050565b634e487b7160e01b600052601160045260246000fd5b634e487b7160e01b600052604160045260246000fdfea264697066735822122081cb55390721778e5ebde06ffdba4100e63be4df1d04c411eb635601e1b13d2164736f6c63430008040033

Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading
Loading...
Loading

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
Loading...
Loading
[ Download: CSV Export  ]

A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.