ETH Price: $2,413.97 (-0.15%)

Contract

0x5233f4C2515ae21B540c438862Abb5603506dEBC
 

Overview

ETH Balance

0 ETH

Eth Value

$0.00

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To
Leverage Positio...208884372024-10-04 0:02:352 days ago1728000155IN
0x5233f4C2...03506dEBC
0 ETH0.002736784.75030743
Leverage Positio...208883982024-10-03 23:54:352 days ago1727999675IN
0x5233f4C2...03506dEBC
0 ETH0.002937363.65124098
Deposit And Leve...208883862024-10-03 23:52:112 days ago1727999531IN
0x5233f4C2...03506dEBC
0 ETH0.001840662.57996018
Deleverage Posit...208883632024-10-03 23:47:352 days ago1727999255IN
0x5233f4C2...03506dEBC
0 ETH0.00177982.66807425
Deleverage Posit...208883422024-10-03 23:43:232 days ago1727999003IN
0x5233f4C2...03506dEBC
0 ETH0.002380072.64596732
Deposit And Leve...208824452024-10-03 3:57:233 days ago1727927843IN
0x5233f4C2...03506dEBC
0 ETH0.002432524.38723011
Leverage Positio...208815202024-10-03 0:51:593 days ago1727916719IN
0x5233f4C2...03506dEBC
0 ETH0.003250115.17603603
Leverage Positio...208812722024-10-03 0:02:233 days ago1727913743IN
0x5233f4C2...03506dEBC
0 ETH0.007263377.218142
Deleverage Posit...208812282024-10-02 23:53:233 days ago1727913203IN
0x5233f4C2...03506dEBC
0 ETH0.003000314.27858893
Deleverage Posit...208812162024-10-02 23:50:593 days ago1727913059IN
0x5233f4C2...03506dEBC
0 ETH0.003502154.49209183
Leverage Positio...208779812024-10-02 13:01:473 days ago1727874107IN
0x5233f4C2...03506dEBC
0 ETH0.0123980210.58865059
Deposit And Leve...208660522024-09-30 21:06:355 days ago1727730395IN
0x5233f4C2...03506dEBC
0 ETH0.0266195817.49402254
Deleverage Posit...208660062024-09-30 20:57:235 days ago1727729843IN
0x5233f4C2...03506dEBC
0 ETH0.0131284215.43513088
Deposit And Leve...208500522024-09-28 15:32:477 days ago1727537567IN
0x5233f4C2...03506dEBC
0 ETH0.005700798.71956194
Deleverage Posit...208497282024-09-28 14:27:477 days ago1727533667IN
0x5233f4C2...03506dEBC
0 ETH0.007684658.81085359
Deleverage Posit...208471372024-09-28 5:47:477 days ago1727502467IN
0x5233f4C2...03506dEBC
0 ETH0.008042297.12556678
Deleverage Posit...208471172024-09-28 5:43:477 days ago1727502227IN
0x5233f4C2...03506dEBC
0 ETH0.010596267.14169108
Deleverage Posit...208470392024-09-28 5:28:117 days ago1727501291IN
0x5233f4C2...03506dEBC
0 ETH0.006296227.55041713
Deleverage Posit...208469922024-09-28 5:18:358 days ago1727500715IN
0x5233f4C2...03506dEBC
0 ETH0.007067517.54768718
Deposit And Leve...208386102024-09-27 1:15:599 days ago1727399759IN
0x5233f4C2...03506dEBC
0 ETH0.0101206913.92699662
Deposit And Leve...208181702024-09-24 4:50:2312 days ago1727153423IN
0x5233f4C2...03506dEBC
0 ETH0.0146741611.81052339
Deposit And Leve...208023052024-09-21 23:43:1114 days ago1726962191IN
0x5233f4C2...03506dEBC
0 ETH0.008178116.0794353
Deposit And Leve...207874742024-09-19 21:57:4716 days ago1726783067IN
0x5233f4C2...03506dEBC
0 ETH0.010270138.48782018
Deposit And Leve...207841462024-09-19 10:48:5916 days ago1726742939IN
0x5233f4C2...03506dEBC
0 ETH0.0223185621.50037076
Deposit And Leve...207774632024-09-18 12:25:5917 days ago1726662359IN
0x5233f4C2...03506dEBC
0 ETH0.007897146.55110377
View all transactions

View more zero value Internal Transactions in Advanced View mode

Advanced mode:
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
ALE

Compiler Version
v0.8.20+commit.a1b79de6

Optimization Enabled:
Yes with 10000 runs

Other Settings:
shanghai EvmVersion
File 1 of 14 : ALE.sol
//SPDX-License-Identifier: None
pragma solidity ^0.8.0;

import "src/interfaces/IMarket.sol";
import "src/interfaces/ITransformHelper.sol";
import {CurveDBRHelper} from "src/util/CurveDBRHelper.sol";
import {Ownable} from "lib/openzeppelin-contracts/contracts/access/Ownable.sol";
import {ReentrancyGuard} from "lib/openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol";
import {IERC20} from "lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";

interface IDBR {
    function markets(address) external view returns (bool);
}

interface IERC3156FlashBorrower {
    /**
     * @dev Receive a flash loan.
     * @param initiator The initiator of the loan.
     * @param token The loan currency.
     * @param amount The amount of tokens lent.
     * @param fee The additional amount of tokens to repay.
     * @param data Arbitrary data structure, intended to contain user-defined parameters.
     * @return The keccak256 hash of "ERC3156FlashBorrower.onFlashLoan"
     */
    function onFlashLoan(
        address initiator,
        address token,
        uint256 amount,
        uint256 fee,
        bytes calldata data
    ) external returns (bytes32);
}

interface IERC3156FlashLender {
    /**
     * @dev Initiate a flash loan.
     * @param receiver The receiver of the tokens in the loan, and the receiver of the callback.
     * @param token The loan currency.
     * @param value The amount of tokens lent.
     * @param data Arbitrary data structure, intended to contain user-defined parameters.
     */
    function flashLoan(
        IERC3156FlashBorrower receiver,
        address token,
        uint256 value,
        bytes calldata data
    ) external returns (bool);
}

// Accelerated leverage engine
contract ALE is
    Ownable,
    ReentrancyGuard,
    CurveDBRHelper,
    IERC3156FlashBorrower
{
    using SafeERC20 for IERC20;
    error CollateralNotSet();
    error MarketNotSet(address market);
    error SwapFailed();
    error DOLAInvalidBorrow(uint256 expected, uint256 actual);
    error DOLAInvalidRepay(uint256 expected, uint256 actual);
    error InvalidProxyAddress();
    error InvalidHelperAddress();
    error InvalidAction(bytes32 action);
    error NotFlashMinter(address caller);
    error NotALE(address caller);
    error NothingToDeposit();
    error DepositFailed(uint256 expected, uint256 actual);
    error WithdrawFailed(uint256 expected, uint256 actual);
    error TotalSupplyChanged(uint256 expected, uint256 actual);
    error CollateralIsZero();
    error NoMarket(address market);
    error MarketSetupFailed(
        address market,
        address buySellToken,
        address collateral,
        address helper
    );

    // 1Inch ExchangeProxy address.
    address payable public exchangeProxy;

    IDBR public constant DBR = IDBR(0xAD038Eb671c44b853887A7E32528FaB35dC5D710);

    IERC3156FlashLender public constant flash =
        IERC3156FlashLender(0x6C5Fdc0c53b122Ae0f15a863C349f3A481DE8f1F);

    bytes32 public constant CALLBACK_SUCCESS =
        keccak256("ERC3156FlashBorrower.onFlashLoan");

    bytes32 public constant LEVERAGE = keccak256("LEVERAGE");
    bytes32 public constant DELEVERAGE = keccak256("DELEVERAGE");

    struct Market {
        IERC20 buySellToken;
        IERC20 collateral;
        ITransformHelper helper;
        bool useProxy;
    }

    struct Permit {
        uint256 deadline;
        uint8 v;
        bytes32 r;
        bytes32 s;
    }

    struct DBRHelper {
        uint256 amountIn; // DOLA or DBR
        uint256 minOut; // DOLA or DBR
        uint256 dola; // DOLA to extra borrow or extra repay
    }

    event LeverageUp(
        address indexed market,
        address indexed account,
        uint256 dolaFlashMinted, // DOLA flash minted for buying collateral only
        uint256 dolaFee, // Flash minter fees paid
        uint256 collateralDeposited, // amount of collateral deposited into the escrow
        uint256 dolaBorrowed, // amount of DOLA borrowed on behalf of the user
        uint256 dolaForDBR // amount of DOLA used for buying DBR
    );

    event LeverageDown(
        address indexed market,
        address indexed account,
        uint256 dolaFlashMinted, // Flash minted DOLA for repaying leverage only
        uint256 dolaFee, // Flash minter fees paid
        uint256 collateralSold, // amount of collateral/underlying sold
        uint256 dolaUserRepaid, // amount of DOLA deposited by the user as part of the repay
        uint256 dbrSoldForDola // amount of DBR sold for DOLA
    );

    event Deposit(
        address indexed market,
        address indexed account,
        address indexed token, // token used for initial deposit (could be collateral or buySellToken)
        uint256 depositAmount
    );

    event NewMarket(
        address indexed market,
        address indexed buySellToken,
        address collateral,
        address indexed helper
    );

    event NewHelper(address indexed market, address indexed helper);

    // Mapping of market to Market structs
    // NOTE: in normal cases sellToken/buyToken is the collateral token,
    // in other cases it could be different (eg. st-yCRV is collateral, yCRV is the token to be swapped from/to DOLA)
    // or with DOLA curve LPs, LP token is the collateral and DOLA is the token to be swapped from/to
    mapping(address => Market) public markets;

    modifier dolaSupplyUnchanged() {
        uint256 totalSupply = dola.totalSupply();
        _;
        if (totalSupply != dola.totalSupply())
            revert TotalSupplyChanged(totalSupply, dola.totalSupply());
    }

    constructor(
        address _exchangeProxy,
        address _pool
    ) Ownable(msg.sender) CurveDBRHelper(_pool) {
        exchangeProxy = payable(address(_exchangeProxy));
        _approveDola(address(flash), type(uint).max);
    }

    function setExchangeProxy(address _exchangeProxy) external onlyOwner {
        if (_exchangeProxy == address(0)) revert InvalidProxyAddress();
        exchangeProxy = payable(_exchangeProxy);
    }

    /// @notice Set the market for a collateral token
    /// @param _buySellToken The token which will be bought/sold (usually the collateral token), probably underlying if there's a helper
    /// @param _market The market contract
    /// @param _helper Optional helper contract to transform collateral to buySelltoken and viceversa
    /// @param useProxy Whether to use the Exchange Proxy or not
    function setMarket(
        address _market,
        address _buySellToken,
        address _helper,
        bool useProxy
    ) external onlyOwner {
        if (!DBR.markets(_market)) revert NoMarket(_market);

        if (_helper == address(0)) {
            if (_buySellToken != IMarket(_market).collateral()) {
                revert MarketSetupFailed(
                    _market,
                    _buySellToken,
                    IMarket(_market).collateral(),
                    _helper
                );
            }
        }

        address collateral = IMarket(_market).collateral();
        markets[_market].buySellToken = IERC20(_buySellToken);
        markets[_market].collateral = IERC20(collateral);
        markets[_market].buySellToken.approve(_market, type(uint256).max);

        if (_buySellToken != collateral) {
            markets[_market].collateral.approve(_market, type(uint256).max);
        }

        if (_helper != address(0)) {
            markets[_market].helper = ITransformHelper(_helper);

            markets[_market].buySellToken.approve(_helper, type(uint256).max);
            markets[_market].collateral.approve(_helper, type(uint256).max);
        }

        markets[_market].useProxy = useProxy;
        emit NewMarket(_market, _buySellToken, collateral, _helper);
    }

    /// @notice Update the helper contract
    /// @param _market The market we want to update the helper contract for
    /// @param _helper The helper contract
    function updateMarketHelper(
        address _market,
        address _helper
    ) external onlyOwner {
        if (address(markets[_market].buySellToken) == address(0))
            revert MarketNotSet(_market);

        address oldHelper = address(markets[_market].helper);
        if (oldHelper != address(0)) {
            markets[_market].buySellToken.approve(oldHelper, 0);
            markets[_market].collateral.approve(oldHelper, 0);
        }

        markets[_market].helper = ITransformHelper(_helper);

        if (_helper != address(0)) {
            markets[_market].buySellToken.approve(_helper, type(uint256).max);
            markets[_market].collateral.approve(_helper, type(uint256).max);
        }

        emit NewHelper(_market, _helper);
    }

    /// @notice Leverage user position by minting DOLA, buying collateral, deposting into the user escrow and borrow DOLA on behalf to repay the minted DOLA
    /// @dev Requires user to sign message to permit the contract to borrow DOLA on behalf
    /// @param value Amount of DOLA to flash mint/burn
    /// @param market The market contract
    /// @param spender The `allowanceTarget` field from the API response.
    /// @param swapCallData The `data` field from the API response.
    /// @param permit Permit data
    /// @param helperData Optional helper data in case the collateral needs to be transformed
    /// @param dbrData Optional data in case the user wants to buy DBR and also withdraw some DOLA
    function leveragePosition(
        uint256 value,
        address market,
        address spender,
        bytes calldata swapCallData,
        Permit calldata permit,
        bytes calldata helperData,
        DBRHelper calldata dbrData
    ) public payable nonReentrant dolaSupplyUnchanged {
        if (address(markets[market].buySellToken) == address(0))
            revert MarketNotSet(market);

        bytes memory data = abi.encode(
            LEVERAGE,
            msg.sender,
            market,
            0, // unused
            spender,
            swapCallData,
            permit,
            helperData,
            dbrData
        );

        flash.flashLoan(
            IERC3156FlashBorrower(address(this)),
            address(dola),
            value,
            data
        );
    }

    /// @notice Deposit collateral and instantly leverage user position by minting DOLA, buying collateral, deposting into the user escrow and borrow DOLA on behalf to repay the minted DOLA
    /// @dev Requires user to sign message to permit the contract to borrow DOLA on behalf
    /// @param initialDeposit Amount of collateral or underlying (in case of helper) to deposit
    /// @param value Amount of DOLA to borrow
    /// @param market The market address
    /// @param spender The `allowanceTarget` field from the API response.
    /// @param swapCallData The `data` field from the API response.
    /// @param permit Permit data
    /// @param helperData Optional helper data in case the collateral needs to be transformed
    /// @param dbrData Optional data in case the user wants to buy DBR and also withdraw some DOLA
    /// @param depositCollateral Whether the initialDeposit is the collateral or the underlying entry asset
    function depositAndLeveragePosition(
        uint256 initialDeposit,
        uint256 value,
        address market,
        address spender,
        bytes calldata swapCallData,
        Permit calldata permit,
        bytes calldata helperData,
        DBRHelper calldata dbrData,
        bool depositCollateral
    ) external payable {
        if (initialDeposit == 0) revert NothingToDeposit();

        IERC20 depositToken;

        if (depositCollateral) {
            depositToken = markets[market].collateral;
        } else {
            depositToken = markets[market].buySellToken;
        }

        depositToken.safeTransferFrom(
            msg.sender,
            address(this),
            initialDeposit
        );
        emit Deposit(market, msg.sender, address(depositToken), initialDeposit);

        leveragePosition(
            value,
            market,
            spender,
            swapCallData,
            permit,
            helperData,
            dbrData
        );
    }

    /// @notice Repay a DOLA loan and withdraw collateral from the escrow
    /// @dev Requires user to sign message to permit the contract to withdraw collateral from the escrow
    /// @param value Amount of DOLA to repay
    /// @param market The market contract
    /// @param collateralAmount Collateral amount to withdraw from the escrow
    /// @param spender The `allowanceTarget` field from the API response.
    /// @param swapCallData The `data` field from the API response.
    /// @param permit Permit data
    /// @param helperData Optional helper data in case collateral needs to be transformed
    /// @param dbrData Optional data in case the user wants to sell DBR
    function deleveragePosition(
        uint256 value,
        address market,
        uint256 collateralAmount,
        address spender,
        bytes calldata swapCallData,
        Permit calldata permit,
        bytes calldata helperData,
        DBRHelper calldata dbrData
    ) external payable nonReentrant dolaSupplyUnchanged {
        if (address(markets[market].buySellToken) == address(0))
            revert MarketNotSet(market);

        bytes memory data = abi.encode(
            DELEVERAGE,
            msg.sender,
            market,
            collateralAmount,
            spender,
            swapCallData,
            permit,
            helperData,
            dbrData
        );

        flash.flashLoan(
            IERC3156FlashBorrower(address(this)),
            address(dola),
            value,
            data
        );
    }

    function onFlashLoan(
        address initiator,
        address,
        uint amount,
        uint fee,
        bytes calldata data
    ) external returns (bytes32) {
        if (initiator != address(this)) revert NotALE(initiator);
        if (msg.sender != address(flash)) revert NotFlashMinter(msg.sender);

        (bytes32 ACTION, , , , , , , , ) = abi.decode(
            data,
            (
                bytes32,
                address,
                address,
                uint256,
                address,
                bytes,
                Permit,
                bytes,
                DBRHelper
            )
        );

        if (ACTION == LEVERAGE) _onFlashLoanLeverage(amount, fee, data);
        else if (ACTION == DELEVERAGE)
            _onFlashLoanDeleverage(amount, fee, data);
        else revert InvalidAction(bytes32(ACTION));

        return CALLBACK_SUCCESS;
    }

    function _onFlashLoanLeverage(
        uint256 _value,
        uint256 _fee,
        bytes memory data
    ) internal {
        (
            ,
            address _user,
            address _market,
            ,
            address _spender,
            bytes memory _swapCallData,
            Permit memory _permit,
            bytes memory _helperData,
            DBRHelper memory _dbrData
        ) = abi.decode(
                data,
                (
                    bytes32,
                    address,
                    address,
                    uint256,
                    address,
                    bytes,
                    Permit,
                    bytes,
                    DBRHelper
                )
            );
        // Call the encoded swap function call on the contract at `swapTarget`,
        // passing along any ETH attached to this function call to cover protocol fees.
        if (markets[_market].useProxy) {
            _approveDola(_spender, _value);
            (bool success, ) = exchangeProxy.call{value: msg.value}(
                _swapCallData
            );
            if (!success) revert SwapFailed();
        }

        // Actual collateral/buyToken bought
        uint256 collateralAmount = markets[_market].buySellToken.balanceOf(
            address(this)
        );
        if (collateralAmount == 0) revert CollateralIsZero();

        // If there's a helper contract, the buyToken has to be transformed
        if (address(markets[_market].helper) != address(0)) {
            collateralAmount = _convertToCollateral(
                collateralAmount,
                _market,
                _helperData
            );
        }

        // Deposit and borrow on behalf
        IMarket(_market).deposit(
            _user,
            markets[_market].collateral.balanceOf(address(this))
        );
        uint256 valuePlusFee = _value + _fee;
        _borrowDola(_user, valuePlusFee, _permit, _dbrData, IMarket(_market));

        if (_dbrData.dola != 0) dola.transfer(_user, _dbrData.dola);

        if (_dbrData.amountIn != 0)
            _buyDbr(_dbrData.amountIn, _dbrData.minOut, _user);
        // Scope to avoid stack too deep error
        {
            uint256 balance = dola.balanceOf(address(this));

            if (balance > valuePlusFee)
                dola.transfer(_user, balance - valuePlusFee);
        }

        // Refund any possible unspent fees to the sender.
        if (address(this).balance > 0)
            payable(_user).transfer(address(this).balance);

        emit LeverageUp(
            _market,
            _user,
            _value,
            _fee,
            collateralAmount,
            _dbrData.dola,
            _dbrData.amountIn
        );
    }

    function _onFlashLoanDeleverage(
        uint256 _value,
        uint256 _fee,
        bytes memory data
    ) internal {
        (
            ,
            address _user,
            address _market,
            uint256 _collateralAmount,
            address _spender,
            bytes memory _swapCallData,
            Permit memory _permit,
            bytes memory _helperData,
            DBRHelper memory _dbrData
        ) = abi.decode(
                data,
                (
                    bytes32,
                    address,
                    address,
                    uint256,
                    address,
                    bytes,
                    Permit,
                    bytes,
                    DBRHelper
                )
            );

        _repayAndWithdraw(
            _user,
            _value,
            _collateralAmount,
            _permit,
            _dbrData,
            IMarket(_market)
        );

        IERC20 sellToken = markets[_market].buySellToken;

        // If there's a helper contract, the collateral has to be transformed
        if (address(markets[_market].helper) != address(0)) {
            _collateralAmount = _convertToAsset(
                _collateralAmount,
                _market,
                sellToken,
                _helperData
            );
            // Reimbourse leftover collateral from conversion if any
            uint256 collateralLeft = markets[_market].collateral.balanceOf(
                address(this)
            );

            if (collateralLeft != 0) {
                markets[_market].collateral.safeTransfer(_user, collateralLeft);
            }
        }

        // Call the encoded swap function call on the contract at `swapTarget`,
        // passing along any ETH attached to this function call to cover protocol fees.
        // NOTE: This will swap the collateral or helperCollateral for DOLA
        if (markets[_market].useProxy) {
            // Approve sellToken for spender
            sellToken.approve(_spender, 0);
            sellToken.approve(_spender, _collateralAmount);
            (bool success, ) = exchangeProxy.call{value: msg.value}(
                _swapCallData
            );
            if (!success) revert SwapFailed();
        }

        if (address(markets[_market].helper) == address(0)) {
            uint256 collateralAvailable = markets[_market].collateral.balanceOf(
                address(this)
            );

            if (collateralAvailable != 0) {
                markets[_market].collateral.safeTransfer(
                    _user,
                    collateralAvailable
                );
            }
        } else if (address(sellToken) != address(dola)) {
            uint256 sellTokenBal = sellToken.balanceOf(address(this));
            // Send any leftover sellToken to the sender
            if (sellTokenBal != 0) sellToken.safeTransfer(_user, sellTokenBal);
        }

        // Scope to avoid stack too deep error
        {
            uint256 balance = dola.balanceOf(address(this));
            if (balance < _value) revert DOLAInvalidRepay(_value, balance);
            uint256 valuePlusFee = _value + _fee;
            // Send any extra DOLA to the sender (in case the collateral withdrawn and swapped exceeds the value to burn)
            if (balance > valuePlusFee)
                dola.transfer(_user, balance - valuePlusFee);
        }

        if (_dbrData.amountIn != 0) {
            dbr.transferFrom(_user, address(this), _dbrData.amountIn);
            _sellDbr(_dbrData.amountIn, _dbrData.minOut, _user);
        }

        // Refund any unspent protocol fees to the sender.
        if (address(this).balance > 0)
            payable(_user).transfer(address(this).balance);

        emit LeverageDown(
            _market,
            _user,
            _value,
            _fee,
            _collateralAmount,
            _dbrData.dola,
            _dbrData.amountIn
        );
    }

    /// @notice Mint DOLA to this contract and approve the spender
    /// @param spender The spender address
    /// @param _value Amount of DOLA to mint and approve
    function _approveDola(address spender, uint256 _value) internal {
        dola.approve(spender, _value);
    }

    /// @notice Borrow DOLA on behalf of the user
    /// @param _value Amount of DOLA to borrow
    /// @param _permit Permit data
    /// @param _dbrData DBR data
    /// @param market The market contract
    function _borrowDola(
        address _user,
        uint256 _value,
        Permit memory _permit,
        DBRHelper memory _dbrData,
        IMarket market
    ) internal {
        uint256 dolaToBorrow = _value;

        if (_dbrData.dola != 0) {
            dolaToBorrow += _dbrData.dola;
        }

        if (_dbrData.amountIn != 0) {
            dolaToBorrow += _dbrData.amountIn;
        }
        // We borrow the amount of DOLA we minted before plus the amount for buying DBR if any
        market.borrowOnBehalf(
            _user,
            dolaToBorrow,
            _permit.deadline,
            _permit.v,
            _permit.r,
            _permit.s
        );

        if (dola.balanceOf(address(this)) < dolaToBorrow)
            revert DOLAInvalidBorrow(
                dolaToBorrow,
                dola.balanceOf(address(this))
            );
    }

    /// @notice Repay DOLA loan and withdraw collateral from the escrow
    /// @param _value Amount of DOLA to repay
    /// @param _collateralAmount Collateral amount to withdraw from the escrow
    /// @param _permit Permit data
    /// @param _dbrData DBR data
    /// @param market The market contract
    function _repayAndWithdraw(
        address _user,
        uint256 _value,
        uint256 _collateralAmount,
        Permit memory _permit,
        DBRHelper memory _dbrData,
        IMarket market
    ) internal {
        if (_dbrData.dola != 0) {
            dola.transferFrom(_user, address(this), _dbrData.dola);
            _approveDola(address(market), _value + _dbrData.dola);
            market.repay(_user, _value + _dbrData.dola);
        } else {
            _approveDola(address(market), _value);
            market.repay(_user, _value);
        }

        // withdraw amount from ZERO EX quote
        market.withdrawOnBehalf(
            _user,
            _collateralAmount,
            _permit.deadline,
            _permit.v,
            _permit.r,
            _permit.s
        );
    }

    /// @notice convert a collateral amount into the underlying asset
    /// @param _collateralAmount Collateral amount to convert
    /// @param _market The market contract
    /// @param sellToken The sell token (the underlying asset)
    /// @param _helperData Optional helper data
    /// @return assetAmount The amount of sellToken/underlying after the conversion
    function _convertToAsset(
        uint256 _collateralAmount,
        address _market,
        IERC20 sellToken,
        bytes memory _helperData
    ) internal returns (uint256) {
        // Collateral amount is now transformed into sellToken
        uint256 assetAmount = markets[_market].helper.transformFromCollateral(
            _collateralAmount,
            _helperData
        );
        uint256 actualAssetAmount = sellToken.balanceOf(address(this));

        if (actualAssetAmount < assetAmount)
            revert WithdrawFailed(assetAmount, actualAssetAmount);

        return actualAssetAmount;
    }

    /// @notice convert the underlying asset amount into the collateral
    /// @param _assetAmount The amount of sellToken/underlying to convert
    /// @param _market The market contract
    /// @param _helperData Optional helper data
    /// @return collateralAmount The amount of collateral after the conversion
    function _convertToCollateral(
        uint256 _assetAmount,
        address _market,
        bytes memory _helperData
    ) internal returns (uint256) {
        // Collateral amount is now transformed
        uint256 collateralAmount = markets[_market]
            .helper
            .transformToCollateral(_assetAmount, _helperData);

        uint256 actualCollateralAmount = markets[_market].collateral.balanceOf(
            address(this)
        );
        if (actualCollateralAmount < collateralAmount)
            revert DepositFailed(collateralAmount, actualCollateralAmount);

        return actualCollateralAmount;
    }

    // solhint-disable-next-line no-empty-blocks
    receive() external payable {}
}

File 2 of 14 : IMarket.sol
pragma solidity ^0.8.13;

import {IBorrowController, IEscrow, IOracle} from "src/Market.sol";

interface IMarket {

    function borrow(uint borrowAmount) external;

    function borrowOnBehalf(
        address msgSender,
        uint dolaAmount,
        uint deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    function withdraw(uint amount) external;

    function withdrawMax() external;

    function withdrawOnBehalf(
        address msgSender,
        uint amount,
        uint deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    function deposit(uint amount) external;

    function deposit(address msgSender, uint collateralAmount) external;

    function depositAndBorrow(
        uint collateralAmount,
        uint borrowAmount
    ) external;

    function repay(address msgSender, uint amount) external;

    function liquidate(address borrower, uint liquidationAmount) external;

    function forceReplenish(address borrower, uint deficitBefore) external;

    function collateral() external returns (address);

    function debts(address user) external returns (uint);

    function recall(uint amount) external;

    function invalidateNonce() external;

    function pauseBorrows(bool paused) external;

    function setBorrowController(IBorrowController borrowController) external;

    function escrows(address user) external view returns (IEscrow);

    function predictEscrow(address user) external view returns (IEscrow);

    function getCollateralValue(address user) external view returns (uint);

    function getWithdrawalLimit(address user) external view returns (uint);

    function getCreditLimit(address user) external view returns (uint);

    function lender() external view returns (address);

    function borrowController() external view returns (address);

    function escrowImplementation() external view returns (address);

    function totalDebt() external view returns (uint);

    function borrowPaused() external view returns (bool);

    function replenishmentIncentiveBps() external view returns (uint);

    function liquidationIncentiveBps() external view returns (uint);

    function collateralFactorBps() external view returns (uint);

    function setCollateralFactorBps(uint cfBps) external;

    function setOracle(IOracle oracle) external;

    function setGov(address newGov) external;

    function setLender(address newLender) external;

    function setPauseGuardian(address newPauseGuardian) external;

    function setReplenismentIncentiveBps(uint riBps) external;

    function setLiquidationIncentiveBps(uint liBps) external;

    function setLiquidationFactorBps(uint lfBps) external;

    function setLiquidationFeeBps(uint lfeeBps) external;

    function liquidationFeeBps() external view returns (uint);

    function DOMAIN_SEPARATOR() external view returns (uint);

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

File 3 of 14 : ITransformHelper.sol
//SPDX-License-Identifier: None
pragma solidity ^0.8.0;

interface ITransformHelper {
    struct Permit {
        uint256 deadline;
        uint8 v;
        bytes32 r;
        bytes32 s;
    }

    function transformToCollateral(
        uint256 amount,
        bytes calldata data
    ) external returns (uint256 collateralAmount);

    function transformToCollateral(
        uint256 amount,
        address recipient,
        bytes calldata data
    ) external returns (uint256 collateralAmount);

    function transformToCollateralAndDeposit(
        uint256 amount,
        address recipient,
        bytes calldata data
    ) external returns (uint256 collateralAmount);

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

    function transformFromCollateral(
        uint256 amount,
        address recipient,
        bytes calldata data
    ) external returns (uint256);

    function withdrawAndTransformFromCollateral(
        uint256 amount,
        address recipient,
        Permit calldata permit,
        bytes calldata data
    ) external returns (uint256 underlyingAmount);

    function assetToCollateralRatio()
        external
        view
        returns (uint256 collateralAmount);

    function assetToCollateral(
        uint256 assetAmount
    ) external view returns (uint256 collateralAmount);

    function collateralToAsset(
        uint256 collateralAmount
    ) external view returns (uint256 assetAmount);
}

File 4 of 14 : CurveDBRHelper.sol
pragma solidity ^0.8.13;
//import "src/util/OffchainAbstractHelper.sol";
import "src/interfaces/IERC20.sol";
import "src/interfaces/IDola.sol";

interface ICurvePool {
    function coins(uint index) external view returns (address);

    function get_dy(uint i, uint j, uint dx) external view returns (uint);

    function exchange(
        uint i,
        uint j,
        uint dx,
        uint min_dy,
        bool use_eth
    ) external payable returns (uint);

    function exchange(
        uint i,
        uint j,
        uint dx,
        uint min_dy,
        bool use_eth,
        address receiver
    ) external payable returns (uint);
}

contract CurveDBRHelper {
    ICurvePool public immutable curvePool;
    IDola constant dola = IDola(0x865377367054516e17014CcdED1e7d814EDC9ce4);
    IERC20 constant dbr = IERC20(0xAD038Eb671c44b853887A7E32528FaB35dC5D710);

    uint dbrIndex;
    uint dolaIndex;

    constructor(address _pool) {
        curvePool = ICurvePool(_pool);
        dola.approve(_pool, type(uint).max);
        dbr.approve(_pool, type(uint).max);
        if (ICurvePool(_pool).coins(0) == address(dola)) {
            dolaIndex = 0;
            dbrIndex = 1;
        } else {
            dolaIndex = 1;
            dbrIndex = 0;
        }
    }

    /**
    @notice Sells an exact amount of DBR for DOLA in a curve pool
    @param amount Amount of DBR to sell
    @param minOut minimum amount of DOLA to receive
    */
    function _sellDbr(uint amount, uint minOut, address receiver) internal {
        if (amount > 0) {
            curvePool.exchange(
                dbrIndex,
                dolaIndex,
                amount,
                minOut,
                false,
                receiver
            );
        }
    }

    /**
    @notice Buys an exact amount of DBR for DOLA in a curve pool
    @param amount Amount of DOLA to sell
    @param minOut minimum amount of DBR out
    */
    function _buyDbr(uint amount, uint minOut, address receiver) internal {
        if (amount > 0) {
            curvePool.exchange(
                dolaIndex,
                dbrIndex,
                amount,
                minOut,
                false,
                receiver
            );
        }
    }

    /**
    @notice Approximates the total amount of dola and dbr needed to borrow a dolaBorrowAmount while also borrowing enough to buy the DBR needed to cover for the borrowing period
    @dev Uses a binary search to approximate the amounts needed. Should only be called as part of generating transaction parameters.
    @param dolaBorrowAmount Amount of dola the user wishes to end up with
    @param period Amount of time in seconds the loan will last
    @param iterations Number of approximation iterations. The higher the more precise the result
    */
    function approximateDolaAndDbrNeeded(
        uint dolaBorrowAmount,
        uint period,
        uint iterations
    ) public view returns (uint dolaForDbr, uint dbrNeeded) {
        uint amountIn = dolaBorrowAmount;
        uint stepSize = amountIn / 2;
        uint dbrReceived = curvePool.get_dy(dolaIndex, dbrIndex, amountIn);
        uint dbrToBuy = ((amountIn + dolaBorrowAmount) * period) / 365 days;
        uint dist = dbrReceived > dbrToBuy
            ? dbrReceived - dbrToBuy
            : dbrToBuy - dbrReceived;
        for (uint i; i < iterations; ++i) {
            uint newAmountIn = amountIn;
            if (dbrReceived > dbrToBuy) {
                newAmountIn -= stepSize;
            } else {
                newAmountIn += stepSize;
            }
            uint newDbrReceived = curvePool.get_dy(
                dolaIndex,
                dbrIndex,
                newAmountIn
            );
            uint newDbrToBuy = ((newAmountIn + dolaBorrowAmount) * period) /
                365 days;
            uint newDist = newDbrReceived > newDbrToBuy
                ? newDbrReceived - newDbrToBuy
                : newDbrToBuy - newDbrReceived;
            if (newDist < dist) {
                dbrReceived = newDbrReceived;
                dbrToBuy = newDbrToBuy;
                dist = newDist;
                amountIn = newAmountIn;
            }
            stepSize /= 2;
        }
        return (amountIn, ((dolaBorrowAmount + amountIn) * period) / 365 days);
    }
}

File 5 of 14 : Ownable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)

pragma solidity ^0.8.20;

import {Context} from "../utils/Context.sol";

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

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

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

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

    /**
     * @dev Initializes the contract setting the address provided by the deployer as the initial owner.
     */
    constructor(address initialOwner) {
        if (initialOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(initialOwner);
    }

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

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

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

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

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

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}

File 6 of 14 : ReentrancyGuard.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/ReentrancyGuard.sol)

pragma solidity ^0.8.20;

/**
 * @dev Contract module that helps prevent reentrant calls to a function.
 *
 * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
 * available, which can be applied to functions to make sure there are no nested
 * (reentrant) calls to them.
 *
 * Note that because there is a single `nonReentrant` guard, functions marked as
 * `nonReentrant` may not call one another. This can be worked around by making
 * those functions `private`, and then adding `external` `nonReentrant` entry
 * points to them.
 *
 * TIP: If you would like to learn more about reentrancy and alternative ways
 * to protect against it, check out our blog post
 * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
 */
abstract contract ReentrancyGuard {
    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

    // The values being non-zero value makes deployment a bit more expensive,
    // but in exchange the refund on every call to nonReentrant will be lower in
    // amount. Since refunds are capped to a percentage of the total
    // transaction's gas, it is best to keep them low in cases like this one, to
    // increase the likelihood of the full refund coming into effect.
    uint256 private constant NOT_ENTERED = 1;
    uint256 private constant ENTERED = 2;

    uint256 private _status;

    /**
     * @dev Unauthorized reentrant call.
     */
    error ReentrancyGuardReentrantCall();

    constructor() {
        _status = NOT_ENTERED;
    }

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and making it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        _nonReentrantBefore();
        _;
        _nonReentrantAfter();
    }

    function _nonReentrantBefore() private {
        // On the first call to nonReentrant, _status will be NOT_ENTERED
        if (_status == ENTERED) {
            revert ReentrancyGuardReentrantCall();
        }

        // Any calls to nonReentrant after this point will fail
        _status = ENTERED;
    }

    function _nonReentrantAfter() private {
        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        _status = NOT_ENTERED;
    }

    /**
     * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
     * `nonReentrant` function in the call stack.
     */
    function _reentrancyGuardEntered() internal view returns (bool) {
        return _status == ENTERED;
    }
}

File 7 of 14 : IERC20.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.20;

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

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

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

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

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

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

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

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

File 8 of 14 : SafeERC20.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.20;

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

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

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

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

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

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

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

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

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

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

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

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

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

        (bool success, bytes memory returndata) = address(token).call(data);
        return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && address(token).code.length > 0;
    }
}

File 9 of 14 : Market.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import "src/interfaces/IERC20.sol";

// Caution. We assume all failed transfers cause reverts and ignore the returned bool.
interface IOracle {
    function getPrice(address,uint) external returns (uint);
    function viewPrice(address,uint) external view returns (uint);
}

interface IEscrow {
    function initialize(IERC20 _token, address beneficiary) external;
    function onDeposit() external;
    function pay(address recipient, uint amount) external;
    function balance() external view returns (uint);
}

interface IDolaBorrowingRights {
    function onBorrow(address user, uint additionalDebt) external;
    function onRepay(address user, uint repaidDebt) external;
    function onForceReplenish(address user, address replenisher, uint amount, uint replenisherReward) external;
    function balanceOf(address user) external view returns (uint);
    function deficitOf(address user) external view returns (uint);
    function replenishmentPriceBps() external view returns (uint);
}

interface IBorrowController {
    function borrowAllowed(address msgSender, address borrower, uint amount) external returns (bool);
    function onRepay(uint amount) external;
}

contract Market {

    address public gov;
    address public lender;
    address public pauseGuardian;
    address public immutable escrowImplementation;
    IDolaBorrowingRights public immutable dbr;
    IBorrowController public borrowController;
    IERC20 public immutable dola = IERC20(0x865377367054516e17014CcdED1e7d814EDC9ce4);
    IERC20 public immutable collateral;
    IOracle public oracle;
    uint public collateralFactorBps;
    uint public replenishmentIncentiveBps;
    uint public liquidationIncentiveBps;
    uint public liquidationFeeBps;
    uint public liquidationFactorBps = 5000; // 50% by default
    bool immutable callOnDepositCallback;
    bool public borrowPaused;
    uint public totalDebt;
    uint256 internal immutable INITIAL_CHAIN_ID;
    bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;
    mapping (address => IEscrow) public escrows; // user => escrow
    mapping (address => uint) public debts; // user => debt
    mapping(address => uint256) public nonces; // user => nonce

    constructor (
        address _gov,
        address _lender,
        address _pauseGuardian,
        address _escrowImplementation,
        IDolaBorrowingRights _dbr,
        IERC20 _collateral,
        IOracle _oracle,
        uint _collateralFactorBps,
        uint _replenishmentIncentiveBps,
        uint _liquidationIncentiveBps,
        bool _callOnDepositCallback
    ) {
        require(_collateralFactorBps < 10000, "Invalid collateral factor");
        require(_liquidationIncentiveBps > 0 && _liquidationIncentiveBps < 10000, "Invalid liquidation incentive");
        require(_replenishmentIncentiveBps < 10000, "Replenishment incentive must be less than 100%");
        gov = _gov;
        lender = _lender;
        pauseGuardian = _pauseGuardian;
        escrowImplementation = _escrowImplementation;
        dbr = _dbr;
        collateral = _collateral;
        oracle = _oracle;
        collateralFactorBps = _collateralFactorBps;
        replenishmentIncentiveBps = _replenishmentIncentiveBps;
        liquidationIncentiveBps = _liquidationIncentiveBps;
        callOnDepositCallback = _callOnDepositCallback;
        INITIAL_CHAIN_ID = block.chainid;
        INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
        if(collateralFactorBps > 0){
            uint unsafeLiquidationIncentive = (10000 - collateralFactorBps) * (liquidationFeeBps + 10000) / collateralFactorBps;
            require(liquidationIncentiveBps < unsafeLiquidationIncentive,  "Liquidation param allow profitable self liquidation");
        }
    }
    
    modifier onlyGov {
        require(msg.sender == gov, "Only gov can call this function");
        _;
    }

    modifier liquidationParamChecker {
        _;
        if(collateralFactorBps > 0){
            uint unsafeLiquidationIncentive = (10000 - collateralFactorBps) * (liquidationFeeBps + 10000) / collateralFactorBps;
            require(liquidationIncentiveBps < unsafeLiquidationIncentive,  "New liquidation param allow profitable self liquidation");
        }
    }

    function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
        return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
    }

    function computeDomainSeparator() internal view virtual returns (bytes32) {
        return
            keccak256(
                abi.encode(
                    keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
                    keccak256(bytes("DBR MARKET")),
                    keccak256("1"),
                    block.chainid,
                    address(this)
                )
            );
    }

    /**
    @notice sets the oracle to a new oracle. Only callable by governance.
    @param _oracle The new oracle conforming to the IOracle interface.
    */
    function setOracle(IOracle _oracle) public onlyGov { oracle = _oracle; }

    /**
    @notice sets the borrow controller to a new borrow controller. Only callable by governance.
    @param _borrowController The new borrow controller conforming to the IBorrowController interface.
    */
    function setBorrowController(IBorrowController _borrowController) public onlyGov { borrowController = _borrowController; }

    /**
    @notice sets the address of governance. Only callable by governance.
    @param _gov Address of the new governance.
    */
    function setGov(address _gov) public onlyGov { gov = _gov; }

    /**
    @notice sets the lender to a new lender. The lender is allowed to recall dola from the contract. Only callable by governance.
    @param _lender Address of the new lender.
    */
    function setLender(address _lender) public onlyGov { lender = _lender; }

    /**
    @notice sets the pause guardian. The pause guardian can pause borrowing. Only callable by governance.
    @param _pauseGuardian Address of the new pauseGuardian.
    */
    function setPauseGuardian(address _pauseGuardian) public onlyGov { pauseGuardian = _pauseGuardian; }
    
    /**
    @notice sets the Collateral Factor requirement of the market as measured in basis points. 1 = 0.01%. Only callable by governance.
    @dev Collateral factor mus be set below 100%
    @param _collateralFactorBps The new collateral factor as measured in basis points. 
    */
    function setCollateralFactorBps(uint _collateralFactorBps) public onlyGov liquidationParamChecker {
        require(_collateralFactorBps < 10000, "Invalid collateral factor");
        collateralFactorBps = _collateralFactorBps;
    }
    
    /**
    @notice sets the Liquidation Factor of the market as denoted in basis points.
     The liquidation Factor denotes the maximum amount of debt that can be liquidated in basis points.
     At 5000, 50% of of a borrower's underwater debt can be liquidated. Only callable by governance.
    @dev Must be set between 1 and 10000.
    @param _liquidationFactorBps The new liquidation factor in basis points. 1 = 0.01%/
    */
    function setLiquidationFactorBps(uint _liquidationFactorBps) public onlyGov {
        require(_liquidationFactorBps > 0 && _liquidationFactorBps <= 10000, "Invalid liquidation factor");
        liquidationFactorBps = _liquidationFactorBps;
    }

    /**
    @notice sets the Replenishment Incentive of the market as denoted in basis points.
     The Replenishment Incentive is the percentage paid out to replenishers on a successful forceReplenish call, denoted in basis points.
    @dev Must be set between 1 and 10000.
    @param _replenishmentIncentiveBps The new replenishment incentive set in basis points. 1 = 0.01%
    */
    function setReplenismentIncentiveBps(uint _replenishmentIncentiveBps) public onlyGov {
        require(_replenishmentIncentiveBps > 0 && _replenishmentIncentiveBps < 10000, "Invalid replenishment incentive");
        replenishmentIncentiveBps = _replenishmentIncentiveBps;
    }

    /**
    @notice sets the Liquidation Incentive of the market as denoted in basis points.
     The Liquidation Incentive is the percentage paid out to liquidators of a borrower's debt when successfully liquidated.
    @dev Must be set between 0 and 10000 - liquidation fee.
    @param _liquidationIncentiveBps The new liqudation incentive set in basis points. 1 = 0.01% 
    */
    function setLiquidationIncentiveBps(uint _liquidationIncentiveBps) public onlyGov liquidationParamChecker {
        require(_liquidationIncentiveBps > 0 && _liquidationIncentiveBps + liquidationFeeBps < 10000, "Invalid liquidation incentive");
        liquidationIncentiveBps = _liquidationIncentiveBps;
    }

    /**
    @notice sets the Liquidation Fee of the market as denoted in basis points.
     The Liquidation Fee is the percentage paid out to governance of a borrower's debt when successfully liquidated.
    @dev Must be set between 0 and 10000 - liquidation factor.
    @param _liquidationFeeBps The new liquidation fee set in basis points. 1 = 0.01%
    */
    function setLiquidationFeeBps(uint _liquidationFeeBps) public onlyGov liquidationParamChecker {
        require(_liquidationFeeBps > 0 && _liquidationFeeBps + liquidationIncentiveBps < 10000, "Invalid liquidation fee");
        liquidationFeeBps = _liquidationFeeBps;
    }

    /**
    @notice Recalls amount of DOLA to the lender.
    @param amount The amount od DOLA to recall to the the lender.
    */
    function recall(uint amount) public {
        require(msg.sender == lender, "Only lender can recall");
        dola.transfer(msg.sender, amount);
    }

    /**
    @notice Pauses or unpauses borrowing for the market. Only gov can unpause a market, while gov and pauseGuardian can pause it.
    @param _value Boolean representing the state pause state of borrows. true = paused, false = unpaused.
    */
    function pauseBorrows(bool _value) public {
        if(_value) {
            require(msg.sender == pauseGuardian || msg.sender == gov, "Only pause guardian or governance can pause");
        } else {
            require(msg.sender == gov, "Only governance can unpause");
        }
        borrowPaused = _value;
    }

    /**
    @notice Internal function for creating an escrow for users to deposit collateral in.
    @dev Uses create2 and minimal proxies to create the escrow at a deterministic address
    @param user The address of the user to create an escrow for.
    */
    function createEscrow(address user) internal returns (IEscrow instance) {
        address implementation = escrowImplementation;
        /// @solidity memory-safe-assembly
        assembly {
            let ptr := mload(0x40)
            mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000)
            mstore(add(ptr, 0x14), shl(0x60, implementation))
            mstore(add(ptr, 0x28), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000)
            instance := create2(0, ptr, 0x37, user)
        }
        require(instance != IEscrow(address(0)), "ERC1167: create2 failed");
        emit CreateEscrow(user, address(instance));
    }

    /**
    @notice Internal function for getting the escrow of a user.
    @dev If the escrow doesn't exist, an escrow contract is deployed.
    @param user The address of the user owning the escrow.
    */
    function getEscrow(address user) internal returns (IEscrow) {
        if(escrows[user] != IEscrow(address(0))) return escrows[user];
        IEscrow escrow = createEscrow(user);
        escrow.initialize(collateral, user);
        escrows[user] = escrow;
        return escrow;
    }

    /**
    @notice Deposit amount of collateral into escrow
    @dev Will deposit the amount into the escrow contract.
    @param amount Amount of collateral token to deposit.
    */
    function deposit(uint amount) public {
        deposit(msg.sender, amount);
    }

    /**
    @notice Deposit and borrow in a single transaction.
    @param amountDeposit Amount of collateral token to deposit into escrow.
    @param amountBorrow Amount of DOLA to borrow.
    */
    function depositAndBorrow(uint amountDeposit, uint amountBorrow) public {
        deposit(amountDeposit);
        borrow(amountBorrow);
    }

    /**
    @notice Deposit amount of collateral into escrow on behalf of msg.sender
    @dev Will deposit the amount into the escrow contract.
    @param user User to deposit on behalf of.
    @param amount Amount of collateral token to deposit.
    */
    function deposit(address user, uint amount) public {
        IEscrow escrow = getEscrow(user);
        collateral.transferFrom(msg.sender, address(escrow), amount);
        if(callOnDepositCallback) {
            escrow.onDeposit();
        }
        emit Deposit(user, amount);
    }

    /**
    @notice View function for predicting the deterministic escrow address of a user.
    @dev Only use deposit() function for deposits and NOT the predicted escrow address unless you know what you're doing
    @param user Address of the user owning the escrow.
    */
    function predictEscrow(address user) public view returns (IEscrow predicted) {
        address implementation = escrowImplementation;
        address deployer = address(this);
        /// @solidity memory-safe-assembly
        assembly {
            let ptr := mload(0x40)
            mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000)
            mstore(add(ptr, 0x14), shl(0x60, implementation))
            mstore(add(ptr, 0x28), 0x5af43d82803e903d91602b57fd5bf3ff00000000000000000000000000000000)
            mstore(add(ptr, 0x38), shl(0x60, deployer))
            mstore(add(ptr, 0x4c), user)
            mstore(add(ptr, 0x6c), keccak256(ptr, 0x37))
            predicted := keccak256(add(ptr, 0x37), 0x55)
        }
    }

    /**
    @notice View function for getting the dollar value of the user's collateral in escrow for the market.
    @param user Address of the user.
    */
    function getCollateralValue(address user) public view returns (uint) {
        IEscrow escrow = predictEscrow(user);
        uint collateralBalance = escrow.balance();
        return collateralBalance * oracle.viewPrice(address(collateral), collateralFactorBps) / 1 ether;
    }

    /**
    @notice Internal function for getting the dollar value of the user's collateral in escrow for the market.
    @dev Updates the lowest price comparisons of the pessimistic oracle
    @param user Address of the user.
    */
    function getCollateralValueInternal(address user) internal returns (uint) {
        IEscrow escrow = predictEscrow(user);
        uint collateralBalance = escrow.balance();
        return collateralBalance * oracle.getPrice(address(collateral), collateralFactorBps) / 1 ether;
    }

    /**
    @notice View function for getting the credit limit of a user.
    @dev To calculate the available credit, subtract user debt from credit limit.
    @param user Address of the user.
    */
    function getCreditLimit(address user) public view returns (uint) {
        uint collateralValue = getCollateralValue(user);
        return collateralValue * collateralFactorBps / 10000;
    }

    /**
    @notice Internal function for getting the credit limit of a user.
    @dev To calculate the available credit, subtract user debt from credit limit. Updates the pessimistic oracle.
    @param user Address of the user.
    */
    function getCreditLimitInternal(address user) internal returns (uint) {
        uint collateralValue = getCollateralValueInternal(user);
        return collateralValue * collateralFactorBps / 10000;
    }
    /**
    @notice Internal function for getting the withdrawal limit of a user.
     The withdrawal limit is how much collateral a user can withdraw before their loan would be underwater. Updates the pessimistic oracle.
    @param user Address of the user.
    */
    function getWithdrawalLimitInternal(address user) internal returns (uint) {
        IEscrow escrow = predictEscrow(user);
        uint collateralBalance = escrow.balance();
        if(collateralBalance == 0) return 0;
        uint debt = debts[user];
        if(debt == 0) return collateralBalance;
        if(collateralFactorBps == 0) return 0;
        uint minimumCollateral = debt * 1 ether / oracle.getPrice(address(collateral), collateralFactorBps) * 10000 / collateralFactorBps;
        if(collateralBalance <= minimumCollateral) return 0;
        return collateralBalance - minimumCollateral;
    }

    /**
    @notice View function for getting the withdrawal limit of a user.
     The withdrawal limit is how much collateral a user can withdraw before their loan would be underwater.
    @param user Address of the user.
    */
    function getWithdrawalLimit(address user) public view returns (uint) {
        IEscrow escrow = predictEscrow(user);
        uint collateralBalance = escrow.balance();
        if(collateralBalance == 0) return 0;
        uint debt = debts[user];
        if(debt == 0) return collateralBalance;
        if(collateralFactorBps == 0) return 0;
        uint minimumCollateral = debt * 1 ether / oracle.viewPrice(address(collateral), collateralFactorBps) * 10000 / collateralFactorBps;
        if(collateralBalance <= minimumCollateral) return 0;
        return collateralBalance - minimumCollateral;
    }

    /**
    @notice Internal function for borrowing DOLA against collateral.
    @dev This internal function is shared between the borrow and borrowOnBehalf function
    @param borrower The address of the borrower that debt will be accrued to.
    @param to The address that will receive the borrowed DOLA
    @param amount The amount of DOLA to be borrowed
    */
    function borrowInternal(address borrower, address to, uint amount) internal {
        require(!borrowPaused, "Borrowing is paused");
        if(borrowController != IBorrowController(address(0))) {
            require(borrowController.borrowAllowed(msg.sender, borrower, amount), "Denied by borrow controller");
        }
        uint credit = getCreditLimitInternal(borrower);
        debts[borrower] += amount;
        require(credit >= debts[borrower], "Exceeded credit limit");
        totalDebt += amount;
        dbr.onBorrow(borrower, amount);
        dola.transfer(to, amount);
        emit Borrow(borrower, amount);
    }

    /**
    @notice Function for borrowing DOLA.
    @dev Will borrow to msg.sender
    @param amount The amount of DOLA to be borrowed.
    */
    function borrow(uint amount) public {
        borrowInternal(msg.sender, msg.sender, amount);
    }

    /**
    @notice Function for using a signed message to borrow on behalf of an address owning an escrow with collateral.
    @dev Signed messaged can be invalidated by incrementing the nonce. Will always borrow to the msg.sender.
    @param from The address of the user being borrowed from
    @param amount The amount to be borrowed
    @param deadline Timestamp after which the signed message will be invalid
    @param v The v param of the ECDSA signature
    @param r The r param of the ECDSA signature
    @param s The s param of the ECDSA signature
    */
    function borrowOnBehalf(address from, uint amount, uint deadline, uint8 v, bytes32 r, bytes32 s) public {
        require(deadline >= block.timestamp, "DEADLINE_EXPIRED");
        unchecked {
            address recoveredAddress = ecrecover(
                keccak256(
                    abi.encodePacked(
                        "\x19\x01",
                        DOMAIN_SEPARATOR(),
                        keccak256(
                            abi.encode(
                                keccak256(
                                    "BorrowOnBehalf(address caller,address from,uint256 amount,uint256 nonce,uint256 deadline)"
                                ),
                                msg.sender,
                                from,
                                amount,
                                nonces[from]++,
                                deadline
                            )
                        )
                    )
                ),
                v,
                r,
                s
            );
            require(recoveredAddress != address(0) && recoveredAddress == from, "INVALID_SIGNER");
            borrowInternal(from, msg.sender, amount);
        }
    }

    /**
    @notice Internal function for withdrawing from the escrow
    @dev The internal function is shared by the withdraw function and withdrawOnBehalf function
    @param from The address owning the escrow to withdraw from.
    @param to The address receiving the tokens
    @param amount The amount being withdrawn.
    */
    function withdrawInternal(address from, address to, uint amount) internal {
        uint limit = getWithdrawalLimitInternal(from);
        require(limit >= amount, "Insufficient withdrawal limit");
        require(dbr.deficitOf(from) == 0, "Can't withdraw with DBR deficit");
        IEscrow escrow = getEscrow(from);
        escrow.pay(to, amount);
        emit Withdraw(from, to, amount);
    }

    /**
    @notice Function for withdrawing to msg.sender.
    @param amount Amount to withdraw.
    */
    function withdraw(uint amount) public {
        withdrawInternal(msg.sender, msg.sender, amount);
    }

    /**
    @notice Function for withdrawing maximum allowed to msg.sender.
    @dev Useful for use with escrows that continously compound tokens, so there won't be dust amounts left
    @dev Dangerous to use when the user has any amount of debt!
    */
    function withdrawMax() public {
        withdrawInternal(msg.sender, msg.sender, getWithdrawalLimitInternal(msg.sender));
    }

    /**
    @notice Function for using a signed message to withdraw on behalf of an address owning an escrow with collateral.
    @dev Signed messaged can be invalidated by incrementing the nonce. Will always withdraw to the msg.sender.
    @param from The address of the user owning the escrow being withdrawn from
    @param amount The amount to be withdrawn
    @param deadline Timestamp after which the signed message will be invalid
    @param v The v param of the ECDSA signature
    @param r The r param of the ECDSA signature
    @param s The s param of the ECDSA signature
    */
    function withdrawOnBehalf(address from, uint amount, uint deadline, uint8 v, bytes32 r, bytes32 s) public {
        require(deadline >= block.timestamp, "DEADLINE_EXPIRED");
        unchecked {
            address recoveredAddress = ecrecover(
                keccak256(
                    abi.encodePacked(
                        "\x19\x01",
                        DOMAIN_SEPARATOR(),
                        keccak256(
                            abi.encode(
                                keccak256(
                                    "WithdrawOnBehalf(address caller,address from,uint256 amount,uint256 nonce,uint256 deadline)"
                                ),
                                msg.sender,
                                from,
                                amount,
                                nonces[from]++,
                                deadline
                            )
                        )
                    )
                ),
                v,
                r,
                s
            );
            require(recoveredAddress != address(0) && recoveredAddress == from, "INVALID_SIGNER");
            withdrawInternal(from, msg.sender, amount);
        }
    }

    /**
    @notice Function for using a signed message to withdraw on behalf of an address owning an escrow with collateral.
    @dev Signed messaged can be invalidated by incrementing the nonce. Will always withdraw to the msg.sender.
    @dev Useful for use with escrows that continously compound tokens, so there won't be dust amounts left
    @dev Dangerous to use when the user has any amount of debt!
    @param from The address of the user owning the escrow being withdrawn from
    @param deadline Timestamp after which the signed message will be invalid
    @param v The v param of the ECDSA signature
    @param r The r param of the ECDSA signature
    @param s The s param of the ECDSA signature
    */
    function withdrawMaxOnBehalf(address from, uint deadline, uint8 v, bytes32 r, bytes32 s) public {
        require(deadline >= block.timestamp, "DEADLINE_EXPIRED");
        unchecked {
            address recoveredAddress = ecrecover(
                keccak256(
                    abi.encodePacked(
                        "\x19\x01",
                        DOMAIN_SEPARATOR(),
                        keccak256(
                            abi.encode(
                                keccak256(
                                    "WithdrawMaxOnBehalf(address caller,address from,uint256 nonce,uint256 deadline)"
                                ),
                                msg.sender,
                                from,
                                nonces[from]++,
                                deadline
                            )
                        )
                    )
                ),
                v,
                r,
                s
            );
            require(recoveredAddress != address(0) && recoveredAddress == from, "INVALID_SIGNER");
            withdrawInternal(from, msg.sender, getWithdrawalLimitInternal(from));
        }
    }

    /**
    @notice Function for incrementing the nonce of the msg.sender, making their latest signed message unusable.
    */
    function invalidateNonce() public {
        nonces[msg.sender]++;
    }
    
    /**
    @notice Function for repaying debt on behalf of user. Debt must be repaid in DOLA.
    @dev If the user has a DBR deficit, they risk initial debt being accrued by forced replenishments.
    @param user Address of the user whose debt is being repaid
    @param amount DOLA amount to be repaid. If set to max uint debt will be repaid in full.
    */
    function repay(address user, uint amount) public {
        uint debt = debts[user];
        if(amount == type(uint).max){
            amount = debt;
        }
        require(debt >= amount, "Repayment greater than debt");
        debts[user] -= amount;
        totalDebt -= amount;
        dbr.onRepay(user, amount);
        if(address(borrowController) != address(0)){
            borrowController.onRepay(amount);
        }
        dola.transferFrom(msg.sender, address(this), amount);
        emit Repay(user, msg.sender, amount);
    }

    /**
    @notice Bundles repayment and withdrawal into a single function call.
    @param repayAmount Amount of DOLA to be repaid
    @param withdrawAmount Amount of underlying to be withdrawn from the escrow
    */
    function repayAndWithdraw(uint repayAmount, uint withdrawAmount) public {
        repay(msg.sender, repayAmount);
        withdraw(withdrawAmount);
    }

    /**
    @notice Function for forcing a user to replenish their DBR deficit at a pre-determined price.
     The replenishment will accrue additional DOLA debt.
     On a successful call, the caller will be paid a replenishment incentive.
    @dev The function will only top the user back up to 0, meaning that the user will have a DBR deficit again in the next block.
    @param user The address of the user being forced to replenish DBR
    @param amount The amount of DBR the user will be replenished.
    */
    function forceReplenish(address user, uint amount) public {
        uint deficit = dbr.deficitOf(user);
        require(deficit > 0, "No DBR deficit");
        require(deficit >= amount, "Amount > deficit");
        uint replenishmentCost = amount * dbr.replenishmentPriceBps() / 10000;
        uint replenisherReward = replenishmentCost * replenishmentIncentiveBps / 10000;
        debts[user] += replenishmentCost;
        uint collateralValue = getCollateralValueInternal(user) * (10000 - liquidationIncentiveBps - liquidationFeeBps) / 10000;
        require(collateralValue >= debts[user], "Exceeded collateral value");
        totalDebt += replenishmentCost;
        dbr.onForceReplenish(user, msg.sender, amount, replenisherReward);
        dola.transfer(msg.sender, replenisherReward);
    }
    /**
    @notice Function for forcing a user to replenish all of their DBR deficit at a pre-determined price.
     The replenishment will accrue additional DOLA debt.
     On a successful call, the caller will be paid a replenishment incentive.
    @dev The function will only top the user back up to 0, meaning that the user will have a DBR deficit again in the next block.
    @param user The address of the user being forced to replenish DBR
    */
    function forceReplenishAll(address user) public {
        uint deficit = dbr.deficitOf(user);
        forceReplenish(user, deficit);
    }

    /**
    @notice Function for liquidating a user's under water debt. Debt is under water when the value of a user's debt is above their collateral factor.
    @param user The user to be liquidated
    @param repaidDebt Th amount of user user debt to liquidate.
    */
    function liquidate(address user, uint repaidDebt) public {
        require(repaidDebt > 0, "Must repay positive debt");
        uint debt = debts[user];
        require(getCreditLimitInternal(user) < debt, "User debt is healthy");
        require(repaidDebt <= debt * liquidationFactorBps / 10000, "Exceeded liquidation factor");
        uint price = oracle.getPrice(address(collateral), collateralFactorBps);
        uint liquidatorReward = repaidDebt * 1 ether / price;
        liquidatorReward += liquidatorReward * liquidationIncentiveBps / 10000;
        debts[user] -= repaidDebt;
        totalDebt -= repaidDebt;
        dbr.onRepay(user, repaidDebt);
        if(address(borrowController) != address(0)){
            borrowController.onRepay(repaidDebt);
        }
        dola.transferFrom(msg.sender, address(this), repaidDebt);
        IEscrow escrow = predictEscrow(user);
        escrow.pay(msg.sender, liquidatorReward);
        if(liquidationFeeBps > 0) {
            uint liquidationFee = repaidDebt * 1 ether / price * liquidationFeeBps / 10000;
            uint balance = escrow.balance();
            if(balance >= liquidationFee) {
                escrow.pay(gov, liquidationFee);
            } else if(balance > 0) {
                escrow.pay(gov, balance);
            }
        }
        emit Liquidate(user, msg.sender, repaidDebt, liquidatorReward);
    }
    
    event Deposit(address indexed account, uint amount);
    event Borrow(address indexed account, uint amount);
    event Withdraw(address indexed account, address indexed to, uint amount);
    event Repay(address indexed account, address indexed repayer, uint amount);
    event Liquidate(address indexed account, address indexed liquidator, uint repaidDebt, uint liquidatorReward);
    event CreateEscrow(address indexed user, address escrow);
}

File 10 of 14 : IERC20.sol
pragma solidity ^0.8.13;

interface IERC20 {
    function approve(address,uint) external;
    function transfer(address,uint) external returns (bool);
    function transferFrom(address,address,uint) external returns (bool);
    function balanceOf(address) external view returns (uint);
    function allowance(address from, address to) external view returns (uint);
}

interface IMintable is IERC20 {
    function mint(address,uint) external;
    function burn(uint) external;
    function addMinter(address minter) external;
}

interface IDelegateableERC20 is IERC20 {
    function delegate(address delegatee) external;
    function delegates(address delegator) external view returns (address delegatee);
}

File 11 of 14 : IDola.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "src/interfaces/IERC20.sol";

interface IDola  is IERC20{
    function mint(address recipient, uint256 amount) external;
    function burn(uint256 amount) external;
    function addMinter(address minter) external;
    function totalSupply() external view returns (uint256);
}

File 12 of 14 : Context.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Context.sol)

pragma solidity ^0.8.20;

/**
 * @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;
    }
}

File 13 of 14 : IERC20Permit.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Permit.sol)

pragma solidity ^0.8.20;

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

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

    /**
     * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
     */
    // solhint-disable-next-line func-name-mixedcase
    function DOMAIN_SEPARATOR() external view returns (bytes32);
}

File 14 of 14 : Address.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Address.sol)

pragma solidity ^0.8.20;

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

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

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

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

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

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

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but also transferring `value` wei to `target`.
     *
     * Requirements:
     *
     * - the calling contract must have an ETH balance of at least `value`.
     * - the called Solidity function must be `payable`.
     */
    function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
        if (address(this).balance < value) {
            revert AddressInsufficientBalance(address(this));
        }
        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a static call.
     */
    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        (bool success, bytes memory returndata) = target.staticcall(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a delegate call.
     */
    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
        (bool success, bytes memory returndata) = target.delegatecall(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

    /**
     * @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target
     * was not a contract or bubbling up the revert reason (falling back to {FailedInnerCall}) in case of an
     * unsuccessful call.
     */
    function verifyCallResultFromTarget(
        address target,
        bool success,
        bytes memory returndata
    ) internal view returns (bytes memory) {
        if (!success) {
            _revert(returndata);
        } else {
            // only check if target is a contract if the call was successful and the return data is empty
            // otherwise we already know that it was a contract
            if (returndata.length == 0 && target.code.length == 0) {
                revert AddressEmptyCode(target);
            }
            return returndata;
        }
    }

    /**
     * @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the
     * revert reason or with a default {FailedInnerCall} error.
     */
    function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
        if (!success) {
            _revert(returndata);
        } else {
            return returndata;
        }
    }

    /**
     * @dev Reverts with returndata if present. Otherwise reverts with {FailedInnerCall}.
     */
    function _revert(bytes memory returndata) private pure {
        // Look for revert reason and bubble it up if present
        if (returndata.length > 0) {
            // The easiest way to bubble the revert reason is using memory via assembly
            /// @solidity memory-safe-assembly
            assembly {
                let returndata_size := mload(returndata)
                revert(add(32, returndata), returndata_size)
            }
        } else {
            revert FailedInnerCall();
        }
    }
}

Settings
{
  "remappings": [
    "@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/",
    "ds-test/=lib/forge-std/lib/ds-test/src/",
    "erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/",
    "forge-std/=lib/forge-std/src/",
    "openzeppelin-contracts/=lib/openzeppelin-contracts/",
    "solmate/=lib/solmate/src/"
  ],
  "optimizer": {
    "enabled": true,
    "runs": 10000
  },
  "metadata": {
    "useLiteralContent": false,
    "bytecodeHash": "ipfs",
    "appendCBOR": true
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "evmVersion": "shanghai",
  "viaIR": false,
  "libraries": {}
}

Contract Security Audit

Contract ABI

[{"inputs":[{"internalType":"address","name":"_exchangeProxy","type":"address"},{"internalType":"address","name":"_pool","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"target","type":"address"}],"name":"AddressEmptyCode","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"AddressInsufficientBalance","type":"error"},{"inputs":[],"name":"CollateralIsZero","type":"error"},{"inputs":[],"name":"CollateralNotSet","type":"error"},{"inputs":[{"internalType":"uint256","name":"expected","type":"uint256"},{"internalType":"uint256","name":"actual","type":"uint256"}],"name":"DOLAInvalidBorrow","type":"error"},{"inputs":[{"internalType":"uint256","name":"expected","type":"uint256"},{"internalType":"uint256","name":"actual","type":"uint256"}],"name":"DOLAInvalidRepay","type":"error"},{"inputs":[{"internalType":"uint256","name":"expected","type":"uint256"},{"internalType":"uint256","name":"actual","type":"uint256"}],"name":"DepositFailed","type":"error"},{"inputs":[],"name":"FailedInnerCall","type":"error"},{"inputs":[{"internalType":"bytes32","name":"action","type":"bytes32"}],"name":"InvalidAction","type":"error"},{"inputs":[],"name":"InvalidHelperAddress","type":"error"},{"inputs":[],"name":"InvalidProxyAddress","type":"error"},{"inputs":[{"internalType":"address","name":"market","type":"address"}],"name":"MarketNotSet","type":"error"},{"inputs":[{"internalType":"address","name":"market","type":"address"},{"internalType":"address","name":"buySellToken","type":"address"},{"internalType":"address","name":"collateral","type":"address"},{"internalType":"address","name":"helper","type":"address"}],"name":"MarketSetupFailed","type":"error"},{"inputs":[{"internalType":"address","name":"market","type":"address"}],"name":"NoMarket","type":"error"},{"inputs":[{"internalType":"address","name":"caller","type":"address"}],"name":"NotALE","type":"error"},{"inputs":[{"internalType":"address","name":"caller","type":"address"}],"name":"NotFlashMinter","type":"error"},{"inputs":[],"name":"NothingToDeposit","type":"error"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"OwnableInvalidOwner","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"OwnableUnauthorizedAccount","type":"error"},{"inputs":[],"name":"ReentrancyGuardReentrantCall","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"SafeERC20FailedOperation","type":"error"},{"inputs":[],"name":"SwapFailed","type":"error"},{"inputs":[{"internalType":"uint256","name":"expected","type":"uint256"},{"internalType":"uint256","name":"actual","type":"uint256"}],"name":"TotalSupplyChanged","type":"error"},{"inputs":[{"internalType":"uint256","name":"expected","type":"uint256"},{"internalType":"uint256","name":"actual","type":"uint256"}],"name":"WithdrawFailed","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"market","type":"address"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"depositAmount","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"market","type":"address"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"dolaFlashMinted","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"dolaFee","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"collateralSold","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"dolaUserRepaid","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"dbrSoldForDola","type":"uint256"}],"name":"LeverageDown","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"market","type":"address"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"dolaFlashMinted","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"dolaFee","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"collateralDeposited","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"dolaBorrowed","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"dolaForDBR","type":"uint256"}],"name":"LeverageUp","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"market","type":"address"},{"indexed":true,"internalType":"address","name":"helper","type":"address"}],"name":"NewHelper","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"market","type":"address"},{"indexed":true,"internalType":"address","name":"buySellToken","type":"address"},{"indexed":false,"internalType":"address","name":"collateral","type":"address"},{"indexed":true,"internalType":"address","name":"helper","type":"address"}],"name":"NewMarket","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"inputs":[],"name":"CALLBACK_SUCCESS","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DBR","outputs":[{"internalType":"contract IDBR","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DELEVERAGE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LEVERAGE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"dolaBorrowAmount","type":"uint256"},{"internalType":"uint256","name":"period","type":"uint256"},{"internalType":"uint256","name":"iterations","type":"uint256"}],"name":"approximateDolaAndDbrNeeded","outputs":[{"internalType":"uint256","name":"dolaForDbr","type":"uint256"},{"internalType":"uint256","name":"dbrNeeded","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"curvePool","outputs":[{"internalType":"contract ICurvePool","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"address","name":"market","type":"address"},{"internalType":"uint256","name":"collateralAmount","type":"uint256"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"bytes","name":"swapCallData","type":"bytes"},{"components":[{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"internalType":"struct ALE.Permit","name":"permit","type":"tuple"},{"internalType":"bytes","name":"helperData","type":"bytes"},{"components":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"minOut","type":"uint256"},{"internalType":"uint256","name":"dola","type":"uint256"}],"internalType":"struct ALE.DBRHelper","name":"dbrData","type":"tuple"}],"name":"deleveragePosition","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"initialDeposit","type":"uint256"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"address","name":"market","type":"address"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"bytes","name":"swapCallData","type":"bytes"},{"components":[{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"internalType":"struct ALE.Permit","name":"permit","type":"tuple"},{"internalType":"bytes","name":"helperData","type":"bytes"},{"components":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"minOut","type":"uint256"},{"internalType":"uint256","name":"dola","type":"uint256"}],"internalType":"struct ALE.DBRHelper","name":"dbrData","type":"tuple"},{"internalType":"bool","name":"depositCollateral","type":"bool"}],"name":"depositAndLeveragePosition","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"exchangeProxy","outputs":[{"internalType":"address payable","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"flash","outputs":[{"internalType":"contract IERC3156FlashLender","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"address","name":"market","type":"address"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"bytes","name":"swapCallData","type":"bytes"},{"components":[{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"internalType":"struct ALE.Permit","name":"permit","type":"tuple"},{"internalType":"bytes","name":"helperData","type":"bytes"},{"components":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"minOut","type":"uint256"},{"internalType":"uint256","name":"dola","type":"uint256"}],"internalType":"struct ALE.DBRHelper","name":"dbrData","type":"tuple"}],"name":"leveragePosition","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"markets","outputs":[{"internalType":"contract IERC20","name":"buySellToken","type":"address"},{"internalType":"contract IERC20","name":"collateral","type":"address"},{"internalType":"contract ITransformHelper","name":"helper","type":"address"},{"internalType":"bool","name":"useProxy","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"initiator","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"fee","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"onFlashLoan","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_exchangeProxy","type":"address"}],"name":"setExchangeProxy","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_market","type":"address"},{"internalType":"address","name":"_buySellToken","type":"address"},{"internalType":"address","name":"_helper","type":"address"},{"internalType":"bool","name":"useProxy","type":"bool"}],"name":"setMarket","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_market","type":"address"},{"internalType":"address","name":"_helper","type":"address"}],"name":"updateMarketHelper","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]

60a060405234801562000010575f80fd5b506040516200452438038062004524833981016040819052620000339162000320565b8033806200005a57604051631e4fbdf760e01b81525f600482015260240160405180910390fd5b620000658162000240565b50600180556001600160a01b038116608081905260405163095ea7b360e01b815260048101919091525f19602482015273865377367054516e17014ccded1e7d814edc9ce49063095ea7b3906044015f604051808303815f87803b158015620000cc575f80fd5b505af1158015620000df573d5f803e3d5ffd5b505060405163095ea7b360e01b81526001600160a01b03841660048201525f19602482015273ad038eb671c44b853887a7e32528fab35dc5d710925063095ea7b391506044015f604051808303815f87803b1580156200013d575f80fd5b505af115801562000150573d5f803e3d5ffd5b505060405163c661065760e01b81525f600482015273865377367054516e17014ccded1e7d814edc9ce492506001600160a01b038416915063c661065790602401602060405180830381865afa158015620001ad573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190620001d3919062000356565b6001600160a01b031603620001f1575f6003556001600255620001fb565b60016003555f6002555b50600480546001600160a01b0319166001600160a01b03841617905562000238736c5fdc0c53b122ae0f15a863c349f3a481de8f1f5f196200028f565b505062000379565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b60405163095ea7b360e01b81526001600160a01b03831660048201526024810182905273865377367054516e17014ccded1e7d814edc9ce49063095ea7b3906044015f604051808303815f87803b158015620002e9575f80fd5b505af1158015620002fc573d5f803e3d5ffd5b505050505050565b80516001600160a01b03811681146200031b575f80fd5b919050565b5f806040838503121562000332575f80fd5b6200033d8362000304565b91506200034d6020840162000304565b90509250929050565b5f6020828403121562000367575f80fd5b620003728262000304565b9392505050565b608051614176620003ae5f395f818161015c01528181610d7701528181610ea901528181612e61015261335c01526141765ff3fe608060405260043610610140575f3560e01c8063a92d64e0116100bb578063d336c82d11610071578063e653bf3a11610057578063e653bf3a14610431578063e6c4efab14610450578063f2fde38b14610463575f80fd5b8063d336c82d146103eb578063dfc0791c14610412575f80fd5b8063b9181d1c116100a1578063b9181d1c14610386578063c7c30be1146103b9578063c9f7e596146103d8575f80fd5b8063a92d64e014610333578063b8b71de114610352575f80fd5b80637c65d3fc116101105780638d01f0ba116100f65780638d01f0ba1461024b5780638da5cb5b1461027e5780638e8f294b1461029a575f80fd5b80637c65d3fc146102055780638237e53814610218575f80fd5b8063218751b21461014b57806323e30c8b1461019b5780636d124715146101c8578063715018a6146101ef575f80fd5b3661014757005b5f80fd5b348015610156575f80fd5b5061017e7f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020015b60405180910390f35b3480156101a6575f80fd5b506101ba6101b53660046135de565b610482565b604051908152602001610192565b3480156101d3575f80fd5b5061017e73ad038eb671c44b853887a7e32528fab35dc5d71081565b3480156101fa575f80fd5b50610203610670565b005b61020361021336600461367b565b610683565b348015610223575f80fd5b506101ba7f439148f0bbc682ca079e46d6e2c2f0c1e3b820f1a291b069d8882abf8cf18dd981565b348015610256575f80fd5b506101ba7f147ae675f296256b5eecc84e9bf2bc391732bfd51dcbff57b4a60ba6cb0ffaf181565b348015610289575f80fd5b505f546001600160a01b031661017e565b3480156102a5575f80fd5b506102fe6102b436600461373a565b60056020525f90815260409020805460018201546002909201546001600160a01b039182169282169181169074010000000000000000000000000000000000000000900460ff1684565b60405161019294939291906001600160a01b039485168152928416602084015292166040820152901515606082015260800190565b34801561033e575f80fd5b5061020361034d366004613755565b610990565b34801561035d575f80fd5b5061037161036c36600461378c565b610d21565b60408051928352602083019190915201610192565b348015610391575f80fd5b506101ba7f0d4f76b1b60020edd6acf708bcdea3786c83a63ebd71b191c28d7561a8c0fc2a81565b3480156103c4575f80fd5b5060045461017e906001600160a01b031681565b6102036103e63660046137cd565b610fd6565b3480156103f6575f80fd5b5061017e736c5fdc0c53b122ae0f15a863c349f3a481de8f1f81565b34801561041d575f80fd5b5061020361042c3660046138af565b6110e0565b34801561043c575f80fd5b5061020361044b36600461373a565b611712565b61020361045e366004613908565b611794565b34801561046e575f80fd5b5061020361047d36600461373a565b611a43565b5f6001600160a01b03871630146104d5576040517f09aeae910000000000000000000000000000000000000000000000000000000081526001600160a01b03881660048201526024015b60405180910390fd5b33736c5fdc0c53b122ae0f15a863c349f3a481de8f1f14610524576040517f3ca399320000000000000000000000000000000000000000000000000000000081523360048201526024016104cc565b5f61053183850185613bc3565b505050505050505090507f147ae675f296256b5eecc84e9bf2bc391732bfd51dcbff57b4a60ba6cb0ffaf181036105a7576105a2868686868080601f0160208091040260200160405190810160405280939291908181526020018383808284375f92019190915250611a9992505050565b610643565b7f0d4f76b1b60020edd6acf708bcdea3786c83a63ebd71b191c28d7561a8c0fc2a810361060e576105a2868686868080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525061208792505050565b6040517fa2160dcf000000000000000000000000000000000000000000000000000000008152600481018290526024016104cc565b507f439148f0bbc682ca079e46d6e2c2f0c1e3b820f1a291b069d8882abf8cf18dd9979650505050505050565b610678612846565b6106815f61288b565b565b61068b6128f2565b5f73865377367054516e17014ccded1e7d814edc9ce46001600160a01b03166318160ddd6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156106dc573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906107009190613c7e565b6001600160a01b038a81165f908152600560205260409020549192501661075e576040517f31589d090000000000000000000000000000000000000000000000000000000081526001600160a01b038a1660048201526024016104cc565b5f7f147ae675f296256b5eecc84e9bf2bc391732bfd51dcbff57b4a60ba6cb0ffaf1338b5f8c8c8c8c8c8c8c6040516020016107a49b9a99989796959493929190613d0b565b6040516020818303038152906040529050736c5fdc0c53b122ae0f15a863c349f3a481de8f1f6001600160a01b0316635cffe9de3073865377367054516e17014ccded1e7d814edc9ce48e856040518563ffffffff1660e01b815260040161080f9493929190613e14565b6020604051808303815f875af115801561082b573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061084f9190613e45565b505073865377367054516e17014ccded1e7d814edc9ce46001600160a01b03166318160ddd6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156108a1573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906108c59190613c7e565b811461097b578073865377367054516e17014ccded1e7d814edc9ce46001600160a01b03166318160ddd6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561091c573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906109409190613c7e565b6040517fc16e0656000000000000000000000000000000000000000000000000000000008152600481019290925260248201526044016104cc565b5061098560018055565b505050505050505050565b610998612846565b6001600160a01b038281165f90815260056020526040902054166109f3576040517f31589d090000000000000000000000000000000000000000000000000000000081526001600160a01b03831660048201526024016104cc565b6001600160a01b038083165f90815260056020526040902060020154168015610b54576001600160a01b038381165f908152600560205260408082205490517f095ea7b3000000000000000000000000000000000000000000000000000000008152848416600482015260248101929092529091169063095ea7b3906044016020604051808303815f875af1158015610a8e573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610ab29190613e45565b506001600160a01b038381165f908152600560205260408082206001015490517f095ea7b3000000000000000000000000000000000000000000000000000000008152848416600482015260248101929092529091169063095ea7b3906044016020604051808303815f875af1158015610b2e573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610b529190613e45565b505b6001600160a01b038381165f90815260056020526040902060020180547fffffffffffffffffffffffff000000000000000000000000000000000000000016918416918217905515610cdc576001600160a01b038381165f90815260056020526040908190205490517f095ea7b300000000000000000000000000000000000000000000000000000000815284831660048201525f19602482015291169063095ea7b3906044016020604051808303815f875af1158015610c17573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610c3b9190613e45565b506001600160a01b038381165f90815260056020526040908190206001015490517f095ea7b300000000000000000000000000000000000000000000000000000000815284831660048201525f19602482015291169063095ea7b3906044016020604051808303815f875af1158015610cb6573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610cda9190613e45565b505b816001600160a01b0316836001600160a01b03167f06bc331032840de3e44179cee67a8860508a9c70737f1e433476e3a9a255b25f60405160405180910390a3505050565b5f808481610d30600283613e8d565b6003546002546040517f556d6e9f00000000000000000000000000000000000000000000000000000000815260048101929092526024820152604481018490529091505f907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063556d6e9f90606401602060405180830381865afa158015610dc4573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610de89190613c7e565b90505f6301e1338088610dfb8b87613ec5565b610e059190613ed8565b610e0f9190613e8d565b90505f818311610e2857610e238383613eef565b610e32565b610e328284613eef565b90505f5b88811015610f9f578583851115610e5857610e518682613eef565b9050610e65565b610e628682613ec5565b90505b6003546002546040517f556d6e9f00000000000000000000000000000000000000000000000000000000815260048101929092526024820152604481018290525f907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063556d6e9f90606401602060405180830381865afa158015610ef6573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610f1a9190613c7e565b90505f6301e133808d8f85610f2f9190613ec5565b610f399190613ed8565b610f439190613e8d565b90505f818311610f5c57610f578383613eef565b610f66565b610f668284613eef565b905085811015610f7d578297508196508095508399505b610f8860028a613e8d565b98505050505080610f9890613f02565b9050610e36565b50846301e133808a610fb1838e613ec5565b610fbb9190613ed8565b610fc59190613e8d565b965096505050505050935093915050565b8a5f0361100f576040517f9a4f66bf00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f811561103957506001600160a01b03808a165f9081526005602052604090206001015416611055565b506001600160a01b03808a165f90815260056020526040902054165b61106a6001600160a01b03821633308f612935565b806001600160a01b0316336001600160a01b03168b6001600160a01b03167f7cfff908a4b583f36430b25d75964c458d8ede8a99bd61be750e97ee1b2f3a968f6040516110b991815260200190565b60405180910390a46110d28b8b8b8b8b8b8b8b8b610683565b505050505050505050505050565b6110e8612846565b6040517f8e8f294b0000000000000000000000000000000000000000000000000000000081526001600160a01b038516600482015273ad038eb671c44b853887a7e32528fab35dc5d71090638e8f294b90602401602060405180830381865afa158015611157573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061117b9190613e45565b6111bc576040517f2df59b680000000000000000000000000000000000000000000000000000000081526001600160a01b03851660048201526024016104cc565b6001600160a01b0382166112fb57836001600160a01b031663d8dfeb456040518163ffffffff1660e01b81526004016020604051808303815f875af1158015611207573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061122b9190613f1a565b6001600160a01b0316836001600160a01b0316146112fb578383856001600160a01b031663d8dfeb456040518163ffffffff1660e01b81526004016020604051808303815f875af1158015611282573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906112a69190613f1a565b6040517f119a599e0000000000000000000000000000000000000000000000000000000081526001600160a01b03938416600482015291831660248301528216604482015290831660648201526084016104cc565b5f846001600160a01b031663d8dfeb456040518163ffffffff1660e01b81526004016020604051808303815f875af1158015611339573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061135d9190613f1a565b6001600160a01b038681165f818152600560205260409081902080547fffffffffffffffffffffffff00000000000000000000000000000000000000009081168a861690811783556001909201805490911694861694909417909355517f095ea7b300000000000000000000000000000000000000000000000000000000815260048101919091525f1960248201529192509063095ea7b3906044016020604051808303815f875af1158015611415573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906114399190613e45565b50806001600160a01b0316846001600160a01b0316146114f4576001600160a01b038581165f81815260056020526040908190206001015490517f095ea7b300000000000000000000000000000000000000000000000000000000815260048101929092525f1960248301529091169063095ea7b3906044016020604051808303815f875af11580156114ce573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906114f29190613e45565b505b6001600160a01b03831615611671576001600160a01b038581165f90815260056020526040908190206002810180547fffffffffffffffffffffffff000000000000000000000000000000000000000016878516908117909155905491517f095ea7b300000000000000000000000000000000000000000000000000000000815260048101919091525f19602482015291169063095ea7b3906044016020604051808303815f875af11580156115ac573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906115d09190613e45565b506001600160a01b038581165f90815260056020526040908190206001015490517f095ea7b300000000000000000000000000000000000000000000000000000000815285831660048201525f19602482015291169063095ea7b3906044016020604051808303815f875af115801561164b573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061166f9190613e45565b505b6001600160a01b038581165f8181526005602090815260409182902060020180547fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff1674010000000000000000000000000000000000000000881515021790559051848416815286841693881692917f8ab0b8c470b101bb508ea695012478540f30a9857917e6c327eda5761d6f8be7910160405180910390a45050505050565b61171a612846565b6001600160a01b03811661175a576040517ffc9dfba700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600480547fffffffffffffffffffffffff0000000000000000000000000000000000000000166001600160a01b0392909216919091179055565b61179c6128f2565b5f73865377367054516e17014ccded1e7d814edc9ce46001600160a01b03166318160ddd6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156117ed573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906118119190613c7e565b6001600160a01b038b81165f908152600560205260409020549192501661186f576040517f31589d090000000000000000000000000000000000000000000000000000000081526001600160a01b038b1660048201526024016104cc565b5f7f0d4f76b1b60020edd6acf708bcdea3786c83a63ebd71b191c28d7561a8c0fc2a338c8c8c8c8c8c8c8c8c6040516020016118b59b9a99989796959493929190613f35565b6040516020818303038152906040529050736c5fdc0c53b122ae0f15a863c349f3a481de8f1f6001600160a01b0316635cffe9de3073865377367054516e17014ccded1e7d814edc9ce48f856040518563ffffffff1660e01b81526004016119209493929190613e14565b6020604051808303815f875af115801561193c573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906119609190613e45565b505073865377367054516e17014ccded1e7d814edc9ce46001600160a01b03166318160ddd6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156119b2573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906119d69190613c7e565b8114611a2d578073865377367054516e17014ccded1e7d814edc9ce46001600160a01b03166318160ddd6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561091c573d5f803e3d5ffd5b50611a3760018055565b50505050505050505050565b611a4b612846565b6001600160a01b038116611a8d576040517f1e4fbdf70000000000000000000000000000000000000000000000000000000081525f60048201526024016104cc565b611a968161288b565b50565b5f805f805f805f87806020019051810190611ab49190614050565b6001600160a01b0387165f90815260056020526040902060020154979f50959d50929b50909950975095509093505074010000000000000000000000000000000000000000900460ff16159050611baa57611b0f858b6129b7565b6004546040515f916001600160a01b0316903490611b2e90889061410d565b5f6040518083038185875af1925050503d805f8114611b68576040519150601f19603f3d011682016040523d82523d5f602084013e611b6d565b606091505b5050905080611ba8576040517f81ceff3000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b505b6001600160a01b038681165f908152600560205260408082205490517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152919216906370a0823190602401602060405180830381865afa158015611c17573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611c3b9190613c7e565b9050805f03611c76576040517fb4f18b0200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6001600160a01b038781165f908152600560205260409020600201541615611ca657611ca3818885612a43565b90505b6001600160a01b038781165f81815260056020526040908190206001015490517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015291926347e7ef24928c92909116906370a0823190602401602060405180830381865afa158015611d21573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611d459190613c7e565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b1681526001600160a01b03909216600483015260248201526044015f604051808303815f87803b158015611da0575f80fd5b505af1158015611db2573d5f803e3d5ffd5b505050505f8a8c611dc39190613ec5565b9050611dd2898287868c612bcb565b604083015115611e7f5760408381015190517fa9059cbb0000000000000000000000000000000000000000000000000000000081526001600160a01b038b166004820152602481019190915273865377367054516e17014ccded1e7d814edc9ce49063a9059cbb906044016020604051808303815f875af1158015611e59573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611e7d9190613e45565b505b825115611e9857611e98835f015184602001518b612dfc565b6040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201525f9073865377367054516e17014ccded1e7d814edc9ce4906370a0823190602401602060405180830381865afa158015611f00573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611f249190613c7e565b905081811115611fd95773865377367054516e17014ccded1e7d814edc9ce463a9059cbb8b611f538585613eef565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b1681526001600160a01b03909216600483015260248201526044016020604051808303815f875af1158015611fb3573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611fd79190613e45565b505b504715612014576040516001600160a01b038a16904780156108fc02915f818181858888f19350505050158015612012573d5f803e3d5ffd5b505b604080840151845182518f8152602081018f9052928301859052606083019190915260808201526001600160a01b03808b1691908a16907f8dd82349e6d3e27a84a3a8de9696a300ca47bc78e5bf4a893dc0ec22ce90f0219060a0015b60405180910390a3505050505050505050505050565b5f805f805f805f80888060200190518101906120a39190614050565b98509850985098509850985098509850506120c2888c8886858c612ed1565b6001600160a01b038088165f90815260056020526040902080546002909101549082169116156121c2576120f887898386613153565b6001600160a01b038981165f908152600560205260408082206001015490517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152939a5090929116906370a0823190602401602060405180830381865afa15801561216c573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906121909190613c7e565b905080156121c0576001600160a01b03808a165f908152600560205260409020600101546121c091168b836132c6565b505b6001600160a01b0388165f9081526005602052604090206002015474010000000000000000000000000000000000000000900460ff16156123ab576040517f095ea7b30000000000000000000000000000000000000000000000000000000081526001600160a01b0387811660048301525f602483015282169063095ea7b3906044016020604051808303815f875af1158015612261573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906122859190613e45565b506040517f095ea7b30000000000000000000000000000000000000000000000000000000081526001600160a01b0387811660048301526024820189905282169063095ea7b3906044016020604051808303815f875af11580156122eb573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061230f9190613e45565b506004546040515f916001600160a01b031690349061232f90899061410d565b5f6040518083038185875af1925050503d805f8114612369576040519150601f19603f3d011682016040523d82523d5f602084013e61236e565b606091505b50509050806123a9576040517f81ceff3000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b505b6001600160a01b038881165f9081526005602052604090206002015416612496576001600160a01b038881165f908152600560205260408082206001015490517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152919216906370a0823190602401602060405180830381865afa15801561243c573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906124609190613c7e565b90508015612490576001600160a01b03808a165f9081526005602052604090206001015461249091168b836132c6565b50612559565b6001600160a01b03811673865377367054516e17014ccded1e7d814edc9ce414612559576040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201525f906001600160a01b038316906370a0823190602401602060405180830381865afa158015612517573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061253b9190613c7e565b90508015612557576125576001600160a01b0383168b836132c6565b505b6040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201525f9073865377367054516e17014ccded1e7d814edc9ce4906370a0823190602401602060405180830381865afa1580156125c1573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906125e59190613c7e565b90508c81101561262b576040517fd694ba95000000000000000000000000000000000000000000000000000000008152600481018e9052602481018290526044016104cc565b5f6126368d8f613ec5565b9050808211156126eb5773865377367054516e17014ccded1e7d814edc9ce463a9059cbb8c6126658486613eef565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b1681526001600160a01b03909216600483015260248201526044016020604051808303815f875af11580156126c5573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906126e99190613e45565b505b50508151156127ab5781516040517f23b872dd0000000000000000000000000000000000000000000000000000000081526001600160a01b038b166004820152306024820152604481019190915273ad038eb671c44b853887a7e32528fab35dc5d710906323b872dd906064016020604051808303815f875af1158015612774573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906127989190613e45565b506127ab825f015183602001518b6132f7565b47156127e5576040516001600160a01b038a16904780156108fc02915f818181858888f193505050501580156127e3573d5f803e3d5ffd5b505b604080830151835182518f8152602081018f90529283018a9052606083019190915260808201526001600160a01b03808b1691908a16907ff065f11216cb555933dc71b81e23e9add41b6496b4daa72d2f4f526b75b029cc9060a001612071565b5f546001600160a01b03163314610681576040517f118cdaa70000000000000000000000000000000000000000000000000000000081523360048201526024016104cc565b5f80546001600160a01b038381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b60026001540361292e576040517f3ee5aeb500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6002600155565b6040516001600160a01b0384811660248301528381166044830152606482018390526129b19186918216906323b872dd906084015b604051602081830303815290604052915060e01b6020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff838183161783525050505061338b565b50505050565b6040517f095ea7b30000000000000000000000000000000000000000000000000000000081526001600160a01b03831660048201526024810182905273865377367054516e17014ccded1e7d814edc9ce49063095ea7b3906044015f604051808303815f87803b158015612a29575f80fd5b505af1158015612a3b573d5f803e3d5ffd5b505050505050565b6001600160a01b038083165f908152600560205260408082206002015490517f2cfa26df00000000000000000000000000000000000000000000000000000000815291928392911690632cfa26df90612aa29088908790600401614128565b6020604051808303815f875af1158015612abe573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612ae29190613c7e565b6001600160a01b038581165f908152600560205260408082206001015490517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015293945090929116906370a0823190602401602060405180830381865afa158015612b56573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612b7a9190613c7e565b905081811015612bc0576040517f278c9d4000000000000000000000000000000000000000000000000000000000815260048101839052602481018290526044016104cc565b9150505b9392505050565b6040820151849015612be9576040830151612be69082613ec5565b90505b825115612bff578251612bfc9082613ec5565b90505b83516020850151604080870151606088015191517f1ef08b750000000000000000000000000000000000000000000000000000000081526001600160a01b038b8116600483015260248201879052604482019590955260ff9093166064840152608483015260a482015290831690631ef08b759060c4015f604051808303815f87803b158015612c8d575f80fd5b505af1158015612c9f573d5f803e3d5ffd5b50506040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015283925073865377367054516e17014ccded1e7d814edc9ce491506370a0823190602401602060405180830381865afa158015612d0b573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612d2f9190613c7e565b1015612a3b576040517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152819073865377367054516e17014ccded1e7d814edc9ce4906370a0823190602401602060405180830381865afa158015612d9d573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612dc19190613c7e565b6040517fc1c3f185000000000000000000000000000000000000000000000000000000008152600481019290925260248201526044016104cc565b8215612ecc576003546002546040517fce7d65030000000000000000000000000000000000000000000000000000000081526004810192909252602482015260448101849052606481018390525f60848201526001600160a01b0382811660a48301527f0000000000000000000000000000000000000000000000000000000000000000169063ce7d65039060c4015b6020604051808303815f875af1158015612ea8573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906129b19190613c7e565b505050565b6040820151156130325760408281015190517f23b872dd0000000000000000000000000000000000000000000000000000000081526001600160a01b0388166004820152306024820152604481019190915273865377367054516e17014ccded1e7d814edc9ce4906323b872dd906064016020604051808303815f875af1158015612f5e573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612f829190613e45565b50612f9c81836040015187612f979190613ec5565b6129b7565b806001600160a01b03166322867d7887846040015188612fbc9190613ec5565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b1681526001600160a01b03909216600483015260248201526044015f604051808303815f87803b158015613017575f80fd5b505af1158015613029573d5f803e3d5ffd5b505050506130b3565b61303c81866129b7565b6040517f22867d780000000000000000000000000000000000000000000000000000000081526001600160a01b038781166004830152602482018790528216906322867d78906044015f604051808303815f87803b15801561309c575f80fd5b505af11580156130ae573d5f803e3d5ffd5b505050505b82516020840151604080860151606087015191517f3525f5910000000000000000000000000000000000000000000000000000000081526001600160a01b038b81166004830152602482018a9052604482019590955260ff9093166064840152608483015260a482015290821690633525f5919060c4015f604051808303815f87803b158015613141575f80fd5b505af1158015611a37573d5f803e3d5ffd5b6001600160a01b038084165f908152600560205260408082206002015490517f4ee5492100000000000000000000000000000000000000000000000000000000815291928392911690634ee54921906131b29089908790600401614128565b6020604051808303815f875af11580156131ce573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906131f29190613c7e565b6040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201529091505f906001600160a01b038616906370a0823190602401602060405180830381865afa158015613252573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906132769190613c7e565b9050818110156132bc576040517fe50972c500000000000000000000000000000000000000000000000000000000815260048101839052602481018290526044016104cc565b9695505050505050565b6040516001600160a01b03838116602483015260448201839052612ecc91859182169063a9059cbb9060640161296a565b8215612ecc576002546003546040517fce7d65030000000000000000000000000000000000000000000000000000000081526004810192909252602482015260448101849052606481018390525f60848201526001600160a01b0382811660a48301527f0000000000000000000000000000000000000000000000000000000000000000169063ce7d65039060c401612e8c565b5f61339f6001600160a01b03841683613405565b905080515f141580156133c35750808060200190518101906133c19190613e45565b155b15612ecc576040517f5274afe70000000000000000000000000000000000000000000000000000000081526001600160a01b03841660048201526024016104cc565b606061341283835f61341b565b90505b92915050565b606081471015613459576040517fcd7860590000000000000000000000000000000000000000000000000000000081523060048201526024016104cc565b5f80856001600160a01b03168486604051613474919061410d565b5f6040518083038185875af1925050503d805f81146134ae576040519150601f19603f3d011682016040523d82523d5f602084013e6134b3565b606091505b50915091506132bc8683836060826134d3576134ce82613533565b612bc4565b81511580156134ea57506001600160a01b0384163b155b1561352c576040517f9996b3150000000000000000000000000000000000000000000000000000000081526001600160a01b03851660048201526024016104cc565b5080612bc4565b8051156135435780518082602001fd5b6040517f1425ea4200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6001600160a01b0381168114611a96575f80fd5b803561359481613575565b919050565b5f8083601f8401126135a9575f80fd5b50813567ffffffffffffffff8111156135c0575f80fd5b6020830191508360208285010111156135d7575f80fd5b9250929050565b5f805f805f8060a087890312156135f3575f80fd5b86356135fe81613575565b9550602087013561360e81613575565b94506040870135935060608701359250608087013567ffffffffffffffff811115613637575f80fd5b61364389828a01613599565b979a9699509497509295939492505050565b5f60808284031215613665575f80fd5b50919050565b5f60608284031215613665575f80fd5b5f805f805f805f805f6101808a8c031215613694575f80fd5b8935985060208a01356136a681613575565b975060408a01356136b681613575565b965060608a013567ffffffffffffffff808211156136d2575f80fd5b6136de8d838e01613599565b90985096508691506136f38d60808e01613655565b95506101008c0135915080821115613709575f80fd5b506137168c828d01613599565b909450925061372b90508b6101208c0161366b565b90509295985092959850929598565b5f6020828403121561374a575f80fd5b8135612bc481613575565b5f8060408385031215613766575f80fd5b823561377181613575565b9150602083013561378181613575565b809150509250929050565b5f805f6060848603121561379e575f80fd5b505081359360208301359350604090920135919050565b8015158114611a96575f80fd5b8035613594816137b5565b5f805f805f805f805f805f6101c08c8e0312156137e8575f80fd5b8b359a5060208c0135995061380060408d0135613575565b60408c0135985061381460608d0135613575565b60608c0135975067ffffffffffffffff8060808e01351115613834575f80fd5b6138448e60808f01358f01613599565b90985096506138568e60a08f01613655565b9550806101208e01351115613869575f80fd5b5061387b8d6101208e01358e01613599565b909450925061388e8d6101408e0161366b565b915061389d6101a08d016137c2565b90509295989b509295989b9093969950565b5f805f80608085870312156138c2575f80fd5b84356138cd81613575565b935060208501356138dd81613575565b925060408501356138ed81613575565b915060608501356138fd816137b5565b939692955090935050565b5f805f805f805f805f806101a08b8d031215613922575f80fd5b8a35995060208b013561393481613575565b985060408b0135975060608b013561394b81613575565b965060808b013567ffffffffffffffff80821115613967575f80fd5b6139738e838f01613599565b90985096508691506139888e60a08f01613655565b95506101208d013591508082111561399e575f80fd5b506139ab8d828e01613599565b90945092506139c090508c6101408d0161366b565b90509295989b9194979a5092959850565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b6040516080810167ffffffffffffffff81118282101715613a2157613a216139d1565b60405290565b6040516060810167ffffffffffffffff81118282101715613a2157613a216139d1565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff81118282101715613a9157613a916139d1565b604052919050565b5f67ffffffffffffffff821115613ab257613ab26139d1565b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01660200190565b5f82601f830112613aed575f80fd5b8135613b00613afb82613a99565b613a4a565b818152846020838601011115613b14575f80fd5b816020850160208301375f918101602001919091529392505050565b60ff81168114611a96575f80fd5b5f60808284031215613b4e575f80fd5b613b566139fe565b9050813581526020820135613b6a81613b30565b80602083015250604082013560408201526060820135606082015292915050565b5f60608284031215613b9b575f80fd5b613ba3613a27565b905081358152602082013560208201526040820135604082015292915050565b5f805f805f805f805f6101c08a8c031215613bdc575f80fd5b8935985060208a0135613bee81613575565b975060408a0135613bfe81613575565b965060608a01359550613c1360808b01613589565b945060a08a013567ffffffffffffffff80821115613c2f575f80fd5b613c3b8d838e01613ade565b9550613c4a8d60c08e01613b3e565b94506101408c0135915080821115613c60575f80fd5b50613c6d8c828d01613ade565b92505061372b8b6101608c01613b8b565b5f60208284031215613c8e575f80fd5b5051919050565b81835281816020850137505f602082840101525f60207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f840116840101905092915050565b803582526020810135613cee81613b30565b60ff16602083015260408181013590830152606090810135910152565b5f6101c08d83526001600160a01b03808e166020850152808d16604085015260ff8c166060850152808b166080850152508060a0840152613d4f818401898b613c95565b9050613d5e60c0840188613cdc565b828103610140840152613d72818688613c95565b8435610160850152602085013561018085015260408501356101a08501529150613d999050565b9c9b505050505050505050505050565b5f5b83811015613dc3578181015183820152602001613dab565b50505f910152565b5f8151808452613de2816020860160208601613da9565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b5f6001600160a01b038087168352808616602084015250836040830152608060608301526132bc6080830184613dcb565b5f60208284031215613e55575f80fd5b8151612bc4816137b5565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f82613ec0577f4e487b71000000000000000000000000000000000000000000000000000000005f52601260045260245ffd5b500490565b8082018082111561341557613415613e60565b808202811582820484141761341557613415613e60565b8181038181111561341557613415613e60565b5f5f198203613f1357613f13613e60565b5060010190565b5f60208284031215613f2a575f80fd5b8151612bc481613575565b5f6101c08d83526001600160a01b03808e166020850152808d1660408501528b6060850152808b166080850152508060a0840152613d4f818401898b613c95565b805161359481613575565b5f82601f830112613f90575f80fd5b8151613f9e613afb82613a99565b818152846020838601011115613fb2575f80fd5b613fc3826020830160208701613da9565b949350505050565b5f60808284031215613fdb575f80fd5b613fe36139fe565b9050815181526020820151613ff781613b30565b80602083015250604082015160408201526060820151606082015292915050565b5f60608284031215614028575f80fd5b614030613a27565b905081518152602082015160208201526040820151604082015292915050565b5f805f805f805f805f6101c08a8c031215614069575f80fd5b8951985060208a015161407b81613575565b60408b015190985061408c81613575565b60608b015190975095506140a260808b01613f76565b945060a08a015167ffffffffffffffff808211156140be575f80fd5b6140ca8d838e01613f81565b95506140d98d60c08e01613fcb565b94506101408c01519150808211156140ef575f80fd5b506140fc8c828d01613f81565b92505061372b8b6101608c01614018565b5f825161411e818460208701613da9565b9190910192915050565b828152604060208201525f613fc36040830184613dcb56fea2646970667358221220ea8b06f971dc883df2dae75c4ebed59e892dabed09416bd973e0bf40c9daabdc64736f6c63430008140033000000000000000000000000111111125421ca6dc452d289314280a0f8842a65000000000000000000000000c7de47b9ca2fc753d6a2f167d8b3e19c6d18b19a

Deployed Bytecode

0x608060405260043610610140575f3560e01c8063a92d64e0116100bb578063d336c82d11610071578063e653bf3a11610057578063e653bf3a14610431578063e6c4efab14610450578063f2fde38b14610463575f80fd5b8063d336c82d146103eb578063dfc0791c14610412575f80fd5b8063b9181d1c116100a1578063b9181d1c14610386578063c7c30be1146103b9578063c9f7e596146103d8575f80fd5b8063a92d64e014610333578063b8b71de114610352575f80fd5b80637c65d3fc116101105780638d01f0ba116100f65780638d01f0ba1461024b5780638da5cb5b1461027e5780638e8f294b1461029a575f80fd5b80637c65d3fc146102055780638237e53814610218575f80fd5b8063218751b21461014b57806323e30c8b1461019b5780636d124715146101c8578063715018a6146101ef575f80fd5b3661014757005b5f80fd5b348015610156575f80fd5b5061017e7f000000000000000000000000c7de47b9ca2fc753d6a2f167d8b3e19c6d18b19a81565b6040516001600160a01b0390911681526020015b60405180910390f35b3480156101a6575f80fd5b506101ba6101b53660046135de565b610482565b604051908152602001610192565b3480156101d3575f80fd5b5061017e73ad038eb671c44b853887a7e32528fab35dc5d71081565b3480156101fa575f80fd5b50610203610670565b005b61020361021336600461367b565b610683565b348015610223575f80fd5b506101ba7f439148f0bbc682ca079e46d6e2c2f0c1e3b820f1a291b069d8882abf8cf18dd981565b348015610256575f80fd5b506101ba7f147ae675f296256b5eecc84e9bf2bc391732bfd51dcbff57b4a60ba6cb0ffaf181565b348015610289575f80fd5b505f546001600160a01b031661017e565b3480156102a5575f80fd5b506102fe6102b436600461373a565b60056020525f90815260409020805460018201546002909201546001600160a01b039182169282169181169074010000000000000000000000000000000000000000900460ff1684565b60405161019294939291906001600160a01b039485168152928416602084015292166040820152901515606082015260800190565b34801561033e575f80fd5b5061020361034d366004613755565b610990565b34801561035d575f80fd5b5061037161036c36600461378c565b610d21565b60408051928352602083019190915201610192565b348015610391575f80fd5b506101ba7f0d4f76b1b60020edd6acf708bcdea3786c83a63ebd71b191c28d7561a8c0fc2a81565b3480156103c4575f80fd5b5060045461017e906001600160a01b031681565b6102036103e63660046137cd565b610fd6565b3480156103f6575f80fd5b5061017e736c5fdc0c53b122ae0f15a863c349f3a481de8f1f81565b34801561041d575f80fd5b5061020361042c3660046138af565b6110e0565b34801561043c575f80fd5b5061020361044b36600461373a565b611712565b61020361045e366004613908565b611794565b34801561046e575f80fd5b5061020361047d36600461373a565b611a43565b5f6001600160a01b03871630146104d5576040517f09aeae910000000000000000000000000000000000000000000000000000000081526001600160a01b03881660048201526024015b60405180910390fd5b33736c5fdc0c53b122ae0f15a863c349f3a481de8f1f14610524576040517f3ca399320000000000000000000000000000000000000000000000000000000081523360048201526024016104cc565b5f61053183850185613bc3565b505050505050505090507f147ae675f296256b5eecc84e9bf2bc391732bfd51dcbff57b4a60ba6cb0ffaf181036105a7576105a2868686868080601f0160208091040260200160405190810160405280939291908181526020018383808284375f92019190915250611a9992505050565b610643565b7f0d4f76b1b60020edd6acf708bcdea3786c83a63ebd71b191c28d7561a8c0fc2a810361060e576105a2868686868080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525061208792505050565b6040517fa2160dcf000000000000000000000000000000000000000000000000000000008152600481018290526024016104cc565b507f439148f0bbc682ca079e46d6e2c2f0c1e3b820f1a291b069d8882abf8cf18dd9979650505050505050565b610678612846565b6106815f61288b565b565b61068b6128f2565b5f73865377367054516e17014ccded1e7d814edc9ce46001600160a01b03166318160ddd6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156106dc573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906107009190613c7e565b6001600160a01b038a81165f908152600560205260409020549192501661075e576040517f31589d090000000000000000000000000000000000000000000000000000000081526001600160a01b038a1660048201526024016104cc565b5f7f147ae675f296256b5eecc84e9bf2bc391732bfd51dcbff57b4a60ba6cb0ffaf1338b5f8c8c8c8c8c8c8c6040516020016107a49b9a99989796959493929190613d0b565b6040516020818303038152906040529050736c5fdc0c53b122ae0f15a863c349f3a481de8f1f6001600160a01b0316635cffe9de3073865377367054516e17014ccded1e7d814edc9ce48e856040518563ffffffff1660e01b815260040161080f9493929190613e14565b6020604051808303815f875af115801561082b573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061084f9190613e45565b505073865377367054516e17014ccded1e7d814edc9ce46001600160a01b03166318160ddd6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156108a1573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906108c59190613c7e565b811461097b578073865377367054516e17014ccded1e7d814edc9ce46001600160a01b03166318160ddd6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561091c573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906109409190613c7e565b6040517fc16e0656000000000000000000000000000000000000000000000000000000008152600481019290925260248201526044016104cc565b5061098560018055565b505050505050505050565b610998612846565b6001600160a01b038281165f90815260056020526040902054166109f3576040517f31589d090000000000000000000000000000000000000000000000000000000081526001600160a01b03831660048201526024016104cc565b6001600160a01b038083165f90815260056020526040902060020154168015610b54576001600160a01b038381165f908152600560205260408082205490517f095ea7b3000000000000000000000000000000000000000000000000000000008152848416600482015260248101929092529091169063095ea7b3906044016020604051808303815f875af1158015610a8e573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610ab29190613e45565b506001600160a01b038381165f908152600560205260408082206001015490517f095ea7b3000000000000000000000000000000000000000000000000000000008152848416600482015260248101929092529091169063095ea7b3906044016020604051808303815f875af1158015610b2e573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610b529190613e45565b505b6001600160a01b038381165f90815260056020526040902060020180547fffffffffffffffffffffffff000000000000000000000000000000000000000016918416918217905515610cdc576001600160a01b038381165f90815260056020526040908190205490517f095ea7b300000000000000000000000000000000000000000000000000000000815284831660048201525f19602482015291169063095ea7b3906044016020604051808303815f875af1158015610c17573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610c3b9190613e45565b506001600160a01b038381165f90815260056020526040908190206001015490517f095ea7b300000000000000000000000000000000000000000000000000000000815284831660048201525f19602482015291169063095ea7b3906044016020604051808303815f875af1158015610cb6573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610cda9190613e45565b505b816001600160a01b0316836001600160a01b03167f06bc331032840de3e44179cee67a8860508a9c70737f1e433476e3a9a255b25f60405160405180910390a3505050565b5f808481610d30600283613e8d565b6003546002546040517f556d6e9f00000000000000000000000000000000000000000000000000000000815260048101929092526024820152604481018490529091505f907f000000000000000000000000c7de47b9ca2fc753d6a2f167d8b3e19c6d18b19a6001600160a01b03169063556d6e9f90606401602060405180830381865afa158015610dc4573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610de89190613c7e565b90505f6301e1338088610dfb8b87613ec5565b610e059190613ed8565b610e0f9190613e8d565b90505f818311610e2857610e238383613eef565b610e32565b610e328284613eef565b90505f5b88811015610f9f578583851115610e5857610e518682613eef565b9050610e65565b610e628682613ec5565b90505b6003546002546040517f556d6e9f00000000000000000000000000000000000000000000000000000000815260048101929092526024820152604481018290525f907f000000000000000000000000c7de47b9ca2fc753d6a2f167d8b3e19c6d18b19a6001600160a01b03169063556d6e9f90606401602060405180830381865afa158015610ef6573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610f1a9190613c7e565b90505f6301e133808d8f85610f2f9190613ec5565b610f399190613ed8565b610f439190613e8d565b90505f818311610f5c57610f578383613eef565b610f66565b610f668284613eef565b905085811015610f7d578297508196508095508399505b610f8860028a613e8d565b98505050505080610f9890613f02565b9050610e36565b50846301e133808a610fb1838e613ec5565b610fbb9190613ed8565b610fc59190613e8d565b965096505050505050935093915050565b8a5f0361100f576040517f9a4f66bf00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f811561103957506001600160a01b03808a165f9081526005602052604090206001015416611055565b506001600160a01b03808a165f90815260056020526040902054165b61106a6001600160a01b03821633308f612935565b806001600160a01b0316336001600160a01b03168b6001600160a01b03167f7cfff908a4b583f36430b25d75964c458d8ede8a99bd61be750e97ee1b2f3a968f6040516110b991815260200190565b60405180910390a46110d28b8b8b8b8b8b8b8b8b610683565b505050505050505050505050565b6110e8612846565b6040517f8e8f294b0000000000000000000000000000000000000000000000000000000081526001600160a01b038516600482015273ad038eb671c44b853887a7e32528fab35dc5d71090638e8f294b90602401602060405180830381865afa158015611157573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061117b9190613e45565b6111bc576040517f2df59b680000000000000000000000000000000000000000000000000000000081526001600160a01b03851660048201526024016104cc565b6001600160a01b0382166112fb57836001600160a01b031663d8dfeb456040518163ffffffff1660e01b81526004016020604051808303815f875af1158015611207573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061122b9190613f1a565b6001600160a01b0316836001600160a01b0316146112fb578383856001600160a01b031663d8dfeb456040518163ffffffff1660e01b81526004016020604051808303815f875af1158015611282573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906112a69190613f1a565b6040517f119a599e0000000000000000000000000000000000000000000000000000000081526001600160a01b03938416600482015291831660248301528216604482015290831660648201526084016104cc565b5f846001600160a01b031663d8dfeb456040518163ffffffff1660e01b81526004016020604051808303815f875af1158015611339573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061135d9190613f1a565b6001600160a01b038681165f818152600560205260409081902080547fffffffffffffffffffffffff00000000000000000000000000000000000000009081168a861690811783556001909201805490911694861694909417909355517f095ea7b300000000000000000000000000000000000000000000000000000000815260048101919091525f1960248201529192509063095ea7b3906044016020604051808303815f875af1158015611415573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906114399190613e45565b50806001600160a01b0316846001600160a01b0316146114f4576001600160a01b038581165f81815260056020526040908190206001015490517f095ea7b300000000000000000000000000000000000000000000000000000000815260048101929092525f1960248301529091169063095ea7b3906044016020604051808303815f875af11580156114ce573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906114f29190613e45565b505b6001600160a01b03831615611671576001600160a01b038581165f90815260056020526040908190206002810180547fffffffffffffffffffffffff000000000000000000000000000000000000000016878516908117909155905491517f095ea7b300000000000000000000000000000000000000000000000000000000815260048101919091525f19602482015291169063095ea7b3906044016020604051808303815f875af11580156115ac573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906115d09190613e45565b506001600160a01b038581165f90815260056020526040908190206001015490517f095ea7b300000000000000000000000000000000000000000000000000000000815285831660048201525f19602482015291169063095ea7b3906044016020604051808303815f875af115801561164b573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061166f9190613e45565b505b6001600160a01b038581165f8181526005602090815260409182902060020180547fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff1674010000000000000000000000000000000000000000881515021790559051848416815286841693881692917f8ab0b8c470b101bb508ea695012478540f30a9857917e6c327eda5761d6f8be7910160405180910390a45050505050565b61171a612846565b6001600160a01b03811661175a576040517ffc9dfba700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600480547fffffffffffffffffffffffff0000000000000000000000000000000000000000166001600160a01b0392909216919091179055565b61179c6128f2565b5f73865377367054516e17014ccded1e7d814edc9ce46001600160a01b03166318160ddd6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156117ed573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906118119190613c7e565b6001600160a01b038b81165f908152600560205260409020549192501661186f576040517f31589d090000000000000000000000000000000000000000000000000000000081526001600160a01b038b1660048201526024016104cc565b5f7f0d4f76b1b60020edd6acf708bcdea3786c83a63ebd71b191c28d7561a8c0fc2a338c8c8c8c8c8c8c8c8c6040516020016118b59b9a99989796959493929190613f35565b6040516020818303038152906040529050736c5fdc0c53b122ae0f15a863c349f3a481de8f1f6001600160a01b0316635cffe9de3073865377367054516e17014ccded1e7d814edc9ce48f856040518563ffffffff1660e01b81526004016119209493929190613e14565b6020604051808303815f875af115801561193c573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906119609190613e45565b505073865377367054516e17014ccded1e7d814edc9ce46001600160a01b03166318160ddd6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156119b2573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906119d69190613c7e565b8114611a2d578073865377367054516e17014ccded1e7d814edc9ce46001600160a01b03166318160ddd6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561091c573d5f803e3d5ffd5b50611a3760018055565b50505050505050505050565b611a4b612846565b6001600160a01b038116611a8d576040517f1e4fbdf70000000000000000000000000000000000000000000000000000000081525f60048201526024016104cc565b611a968161288b565b50565b5f805f805f805f87806020019051810190611ab49190614050565b6001600160a01b0387165f90815260056020526040902060020154979f50959d50929b50909950975095509093505074010000000000000000000000000000000000000000900460ff16159050611baa57611b0f858b6129b7565b6004546040515f916001600160a01b0316903490611b2e90889061410d565b5f6040518083038185875af1925050503d805f8114611b68576040519150601f19603f3d011682016040523d82523d5f602084013e611b6d565b606091505b5050905080611ba8576040517f81ceff3000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b505b6001600160a01b038681165f908152600560205260408082205490517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152919216906370a0823190602401602060405180830381865afa158015611c17573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611c3b9190613c7e565b9050805f03611c76576040517fb4f18b0200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6001600160a01b038781165f908152600560205260409020600201541615611ca657611ca3818885612a43565b90505b6001600160a01b038781165f81815260056020526040908190206001015490517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015291926347e7ef24928c92909116906370a0823190602401602060405180830381865afa158015611d21573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611d459190613c7e565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b1681526001600160a01b03909216600483015260248201526044015f604051808303815f87803b158015611da0575f80fd5b505af1158015611db2573d5f803e3d5ffd5b505050505f8a8c611dc39190613ec5565b9050611dd2898287868c612bcb565b604083015115611e7f5760408381015190517fa9059cbb0000000000000000000000000000000000000000000000000000000081526001600160a01b038b166004820152602481019190915273865377367054516e17014ccded1e7d814edc9ce49063a9059cbb906044016020604051808303815f875af1158015611e59573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611e7d9190613e45565b505b825115611e9857611e98835f015184602001518b612dfc565b6040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201525f9073865377367054516e17014ccded1e7d814edc9ce4906370a0823190602401602060405180830381865afa158015611f00573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611f249190613c7e565b905081811115611fd95773865377367054516e17014ccded1e7d814edc9ce463a9059cbb8b611f538585613eef565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b1681526001600160a01b03909216600483015260248201526044016020604051808303815f875af1158015611fb3573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611fd79190613e45565b505b504715612014576040516001600160a01b038a16904780156108fc02915f818181858888f19350505050158015612012573d5f803e3d5ffd5b505b604080840151845182518f8152602081018f9052928301859052606083019190915260808201526001600160a01b03808b1691908a16907f8dd82349e6d3e27a84a3a8de9696a300ca47bc78e5bf4a893dc0ec22ce90f0219060a0015b60405180910390a3505050505050505050505050565b5f805f805f805f80888060200190518101906120a39190614050565b98509850985098509850985098509850506120c2888c8886858c612ed1565b6001600160a01b038088165f90815260056020526040902080546002909101549082169116156121c2576120f887898386613153565b6001600160a01b038981165f908152600560205260408082206001015490517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152939a5090929116906370a0823190602401602060405180830381865afa15801561216c573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906121909190613c7e565b905080156121c0576001600160a01b03808a165f908152600560205260409020600101546121c091168b836132c6565b505b6001600160a01b0388165f9081526005602052604090206002015474010000000000000000000000000000000000000000900460ff16156123ab576040517f095ea7b30000000000000000000000000000000000000000000000000000000081526001600160a01b0387811660048301525f602483015282169063095ea7b3906044016020604051808303815f875af1158015612261573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906122859190613e45565b506040517f095ea7b30000000000000000000000000000000000000000000000000000000081526001600160a01b0387811660048301526024820189905282169063095ea7b3906044016020604051808303815f875af11580156122eb573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061230f9190613e45565b506004546040515f916001600160a01b031690349061232f90899061410d565b5f6040518083038185875af1925050503d805f8114612369576040519150601f19603f3d011682016040523d82523d5f602084013e61236e565b606091505b50509050806123a9576040517f81ceff3000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b505b6001600160a01b038881165f9081526005602052604090206002015416612496576001600160a01b038881165f908152600560205260408082206001015490517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152919216906370a0823190602401602060405180830381865afa15801561243c573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906124609190613c7e565b90508015612490576001600160a01b03808a165f9081526005602052604090206001015461249091168b836132c6565b50612559565b6001600160a01b03811673865377367054516e17014ccded1e7d814edc9ce414612559576040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201525f906001600160a01b038316906370a0823190602401602060405180830381865afa158015612517573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061253b9190613c7e565b90508015612557576125576001600160a01b0383168b836132c6565b505b6040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201525f9073865377367054516e17014ccded1e7d814edc9ce4906370a0823190602401602060405180830381865afa1580156125c1573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906125e59190613c7e565b90508c81101561262b576040517fd694ba95000000000000000000000000000000000000000000000000000000008152600481018e9052602481018290526044016104cc565b5f6126368d8f613ec5565b9050808211156126eb5773865377367054516e17014ccded1e7d814edc9ce463a9059cbb8c6126658486613eef565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b1681526001600160a01b03909216600483015260248201526044016020604051808303815f875af11580156126c5573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906126e99190613e45565b505b50508151156127ab5781516040517f23b872dd0000000000000000000000000000000000000000000000000000000081526001600160a01b038b166004820152306024820152604481019190915273ad038eb671c44b853887a7e32528fab35dc5d710906323b872dd906064016020604051808303815f875af1158015612774573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906127989190613e45565b506127ab825f015183602001518b6132f7565b47156127e5576040516001600160a01b038a16904780156108fc02915f818181858888f193505050501580156127e3573d5f803e3d5ffd5b505b604080830151835182518f8152602081018f90529283018a9052606083019190915260808201526001600160a01b03808b1691908a16907ff065f11216cb555933dc71b81e23e9add41b6496b4daa72d2f4f526b75b029cc9060a001612071565b5f546001600160a01b03163314610681576040517f118cdaa70000000000000000000000000000000000000000000000000000000081523360048201526024016104cc565b5f80546001600160a01b038381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b60026001540361292e576040517f3ee5aeb500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6002600155565b6040516001600160a01b0384811660248301528381166044830152606482018390526129b19186918216906323b872dd906084015b604051602081830303815290604052915060e01b6020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff838183161783525050505061338b565b50505050565b6040517f095ea7b30000000000000000000000000000000000000000000000000000000081526001600160a01b03831660048201526024810182905273865377367054516e17014ccded1e7d814edc9ce49063095ea7b3906044015f604051808303815f87803b158015612a29575f80fd5b505af1158015612a3b573d5f803e3d5ffd5b505050505050565b6001600160a01b038083165f908152600560205260408082206002015490517f2cfa26df00000000000000000000000000000000000000000000000000000000815291928392911690632cfa26df90612aa29088908790600401614128565b6020604051808303815f875af1158015612abe573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612ae29190613c7e565b6001600160a01b038581165f908152600560205260408082206001015490517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015293945090929116906370a0823190602401602060405180830381865afa158015612b56573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612b7a9190613c7e565b905081811015612bc0576040517f278c9d4000000000000000000000000000000000000000000000000000000000815260048101839052602481018290526044016104cc565b9150505b9392505050565b6040820151849015612be9576040830151612be69082613ec5565b90505b825115612bff578251612bfc9082613ec5565b90505b83516020850151604080870151606088015191517f1ef08b750000000000000000000000000000000000000000000000000000000081526001600160a01b038b8116600483015260248201879052604482019590955260ff9093166064840152608483015260a482015290831690631ef08b759060c4015f604051808303815f87803b158015612c8d575f80fd5b505af1158015612c9f573d5f803e3d5ffd5b50506040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015283925073865377367054516e17014ccded1e7d814edc9ce491506370a0823190602401602060405180830381865afa158015612d0b573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612d2f9190613c7e565b1015612a3b576040517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152819073865377367054516e17014ccded1e7d814edc9ce4906370a0823190602401602060405180830381865afa158015612d9d573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612dc19190613c7e565b6040517fc1c3f185000000000000000000000000000000000000000000000000000000008152600481019290925260248201526044016104cc565b8215612ecc576003546002546040517fce7d65030000000000000000000000000000000000000000000000000000000081526004810192909252602482015260448101849052606481018390525f60848201526001600160a01b0382811660a48301527f000000000000000000000000c7de47b9ca2fc753d6a2f167d8b3e19c6d18b19a169063ce7d65039060c4015b6020604051808303815f875af1158015612ea8573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906129b19190613c7e565b505050565b6040820151156130325760408281015190517f23b872dd0000000000000000000000000000000000000000000000000000000081526001600160a01b0388166004820152306024820152604481019190915273865377367054516e17014ccded1e7d814edc9ce4906323b872dd906064016020604051808303815f875af1158015612f5e573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612f829190613e45565b50612f9c81836040015187612f979190613ec5565b6129b7565b806001600160a01b03166322867d7887846040015188612fbc9190613ec5565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b1681526001600160a01b03909216600483015260248201526044015f604051808303815f87803b158015613017575f80fd5b505af1158015613029573d5f803e3d5ffd5b505050506130b3565b61303c81866129b7565b6040517f22867d780000000000000000000000000000000000000000000000000000000081526001600160a01b038781166004830152602482018790528216906322867d78906044015f604051808303815f87803b15801561309c575f80fd5b505af11580156130ae573d5f803e3d5ffd5b505050505b82516020840151604080860151606087015191517f3525f5910000000000000000000000000000000000000000000000000000000081526001600160a01b038b81166004830152602482018a9052604482019590955260ff9093166064840152608483015260a482015290821690633525f5919060c4015f604051808303815f87803b158015613141575f80fd5b505af1158015611a37573d5f803e3d5ffd5b6001600160a01b038084165f908152600560205260408082206002015490517f4ee5492100000000000000000000000000000000000000000000000000000000815291928392911690634ee54921906131b29089908790600401614128565b6020604051808303815f875af11580156131ce573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906131f29190613c7e565b6040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201529091505f906001600160a01b038616906370a0823190602401602060405180830381865afa158015613252573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906132769190613c7e565b9050818110156132bc576040517fe50972c500000000000000000000000000000000000000000000000000000000815260048101839052602481018290526044016104cc565b9695505050505050565b6040516001600160a01b03838116602483015260448201839052612ecc91859182169063a9059cbb9060640161296a565b8215612ecc576002546003546040517fce7d65030000000000000000000000000000000000000000000000000000000081526004810192909252602482015260448101849052606481018390525f60848201526001600160a01b0382811660a48301527f000000000000000000000000c7de47b9ca2fc753d6a2f167d8b3e19c6d18b19a169063ce7d65039060c401612e8c565b5f61339f6001600160a01b03841683613405565b905080515f141580156133c35750808060200190518101906133c19190613e45565b155b15612ecc576040517f5274afe70000000000000000000000000000000000000000000000000000000081526001600160a01b03841660048201526024016104cc565b606061341283835f61341b565b90505b92915050565b606081471015613459576040517fcd7860590000000000000000000000000000000000000000000000000000000081523060048201526024016104cc565b5f80856001600160a01b03168486604051613474919061410d565b5f6040518083038185875af1925050503d805f81146134ae576040519150601f19603f3d011682016040523d82523d5f602084013e6134b3565b606091505b50915091506132bc8683836060826134d3576134ce82613533565b612bc4565b81511580156134ea57506001600160a01b0384163b155b1561352c576040517f9996b3150000000000000000000000000000000000000000000000000000000081526001600160a01b03851660048201526024016104cc565b5080612bc4565b8051156135435780518082602001fd5b6040517f1425ea4200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6001600160a01b0381168114611a96575f80fd5b803561359481613575565b919050565b5f8083601f8401126135a9575f80fd5b50813567ffffffffffffffff8111156135c0575f80fd5b6020830191508360208285010111156135d7575f80fd5b9250929050565b5f805f805f8060a087890312156135f3575f80fd5b86356135fe81613575565b9550602087013561360e81613575565b94506040870135935060608701359250608087013567ffffffffffffffff811115613637575f80fd5b61364389828a01613599565b979a9699509497509295939492505050565b5f60808284031215613665575f80fd5b50919050565b5f60608284031215613665575f80fd5b5f805f805f805f805f6101808a8c031215613694575f80fd5b8935985060208a01356136a681613575565b975060408a01356136b681613575565b965060608a013567ffffffffffffffff808211156136d2575f80fd5b6136de8d838e01613599565b90985096508691506136f38d60808e01613655565b95506101008c0135915080821115613709575f80fd5b506137168c828d01613599565b909450925061372b90508b6101208c0161366b565b90509295985092959850929598565b5f6020828403121561374a575f80fd5b8135612bc481613575565b5f8060408385031215613766575f80fd5b823561377181613575565b9150602083013561378181613575565b809150509250929050565b5f805f6060848603121561379e575f80fd5b505081359360208301359350604090920135919050565b8015158114611a96575f80fd5b8035613594816137b5565b5f805f805f805f805f805f6101c08c8e0312156137e8575f80fd5b8b359a5060208c0135995061380060408d0135613575565b60408c0135985061381460608d0135613575565b60608c0135975067ffffffffffffffff8060808e01351115613834575f80fd5b6138448e60808f01358f01613599565b90985096506138568e60a08f01613655565b9550806101208e01351115613869575f80fd5b5061387b8d6101208e01358e01613599565b909450925061388e8d6101408e0161366b565b915061389d6101a08d016137c2565b90509295989b509295989b9093969950565b5f805f80608085870312156138c2575f80fd5b84356138cd81613575565b935060208501356138dd81613575565b925060408501356138ed81613575565b915060608501356138fd816137b5565b939692955090935050565b5f805f805f805f805f806101a08b8d031215613922575f80fd5b8a35995060208b013561393481613575565b985060408b0135975060608b013561394b81613575565b965060808b013567ffffffffffffffff80821115613967575f80fd5b6139738e838f01613599565b90985096508691506139888e60a08f01613655565b95506101208d013591508082111561399e575f80fd5b506139ab8d828e01613599565b90945092506139c090508c6101408d0161366b565b90509295989b9194979a5092959850565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b6040516080810167ffffffffffffffff81118282101715613a2157613a216139d1565b60405290565b6040516060810167ffffffffffffffff81118282101715613a2157613a216139d1565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff81118282101715613a9157613a916139d1565b604052919050565b5f67ffffffffffffffff821115613ab257613ab26139d1565b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01660200190565b5f82601f830112613aed575f80fd5b8135613b00613afb82613a99565b613a4a565b818152846020838601011115613b14575f80fd5b816020850160208301375f918101602001919091529392505050565b60ff81168114611a96575f80fd5b5f60808284031215613b4e575f80fd5b613b566139fe565b9050813581526020820135613b6a81613b30565b80602083015250604082013560408201526060820135606082015292915050565b5f60608284031215613b9b575f80fd5b613ba3613a27565b905081358152602082013560208201526040820135604082015292915050565b5f805f805f805f805f6101c08a8c031215613bdc575f80fd5b8935985060208a0135613bee81613575565b975060408a0135613bfe81613575565b965060608a01359550613c1360808b01613589565b945060a08a013567ffffffffffffffff80821115613c2f575f80fd5b613c3b8d838e01613ade565b9550613c4a8d60c08e01613b3e565b94506101408c0135915080821115613c60575f80fd5b50613c6d8c828d01613ade565b92505061372b8b6101608c01613b8b565b5f60208284031215613c8e575f80fd5b5051919050565b81835281816020850137505f602082840101525f60207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f840116840101905092915050565b803582526020810135613cee81613b30565b60ff16602083015260408181013590830152606090810135910152565b5f6101c08d83526001600160a01b03808e166020850152808d16604085015260ff8c166060850152808b166080850152508060a0840152613d4f818401898b613c95565b9050613d5e60c0840188613cdc565b828103610140840152613d72818688613c95565b8435610160850152602085013561018085015260408501356101a08501529150613d999050565b9c9b505050505050505050505050565b5f5b83811015613dc3578181015183820152602001613dab565b50505f910152565b5f8151808452613de2816020860160208601613da9565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b5f6001600160a01b038087168352808616602084015250836040830152608060608301526132bc6080830184613dcb565b5f60208284031215613e55575f80fd5b8151612bc4816137b5565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f82613ec0577f4e487b71000000000000000000000000000000000000000000000000000000005f52601260045260245ffd5b500490565b8082018082111561341557613415613e60565b808202811582820484141761341557613415613e60565b8181038181111561341557613415613e60565b5f5f198203613f1357613f13613e60565b5060010190565b5f60208284031215613f2a575f80fd5b8151612bc481613575565b5f6101c08d83526001600160a01b03808e166020850152808d1660408501528b6060850152808b166080850152508060a0840152613d4f818401898b613c95565b805161359481613575565b5f82601f830112613f90575f80fd5b8151613f9e613afb82613a99565b818152846020838601011115613fb2575f80fd5b613fc3826020830160208701613da9565b949350505050565b5f60808284031215613fdb575f80fd5b613fe36139fe565b9050815181526020820151613ff781613b30565b80602083015250604082015160408201526060820151606082015292915050565b5f60608284031215614028575f80fd5b614030613a27565b905081518152602082015160208201526040820151604082015292915050565b5f805f805f805f805f6101c08a8c031215614069575f80fd5b8951985060208a015161407b81613575565b60408b015190985061408c81613575565b60608b015190975095506140a260808b01613f76565b945060a08a015167ffffffffffffffff808211156140be575f80fd5b6140ca8d838e01613f81565b95506140d98d60c08e01613fcb565b94506101408c01519150808211156140ef575f80fd5b506140fc8c828d01613f81565b92505061372b8b6101608c01614018565b5f825161411e818460208701613da9565b9190910192915050565b828152604060208201525f613fc36040830184613dcb56fea2646970667358221220ea8b06f971dc883df2dae75c4ebed59e892dabed09416bd973e0bf40c9daabdc64736f6c63430008140033

Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)

000000000000000000000000111111125421ca6dc452d289314280a0f8842a65000000000000000000000000c7de47b9ca2fc753d6a2f167d8b3e19c6d18b19a

-----Decoded View---------------
Arg [0] : _exchangeProxy (address): 0x111111125421cA6dc452d289314280a0f8842A65
Arg [1] : _pool (address): 0xC7DE47b9Ca2Fc753D6a2F167D8b3e19c6D18b19a

-----Encoded View---------------
2 Constructor Arguments found :
Arg [0] : 000000000000000000000000111111125421ca6dc452d289314280a0f8842a65
Arg [1] : 000000000000000000000000c7de47b9ca2fc753d6a2f167d8b3e19c6d18b19a


Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

Block Uncle Number Difficulty Gas Used Reward
View All Uncles
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.