contract ZeroExV3ExchangeWrapper is
    ExchangeWrapper,
    ExchangeReader
{
    bytes4 constant internal ERC20_PROXY_ID = 0xf47261b0;
    bytes4 constant internal ERC20_BRIDGE_PROXY_ID = 0xdc1600f3;

    using LibBytes for bytes;
    using SafeMath for uint256;
    using TokenInteract for address;
    using AdvancedTokenInteract for address;

    // ============ State Variables ============

    // address of the ZeroEx V3 Exchange
    address public ZERO_EX_EXCHANGE;

    // address of the ZeroEx V3 ERC20Proxy
    address public ZERO_EX_TOKEN_PROXY;

    // Canonical WETH token
    WETH9 public WETH;

    // ============ Constructor ============

    constructor(
        address zeroExExchange,
        address zeroExProxy,
        address payable wethToken
    )
        public
    {
        ZERO_EX_EXCHANGE = zeroExExchange;
        ZERO_EX_TOKEN_PROXY = zeroExProxy;
        WETH = WETH9(wethToken);
    }

    // ============ Public Functions ============

    /**
     * Fallback function. Disallows ether to be sent to this contract without data except when
     * unwrapping WETH.
     */
    function()
        external
        payable
    {
        require(
            msg.sender == address(WETH),
            "Cannot receive ETH"
        );
    }

    /**
     * Exchange some amount of takerToken for makerToken.
     *
     * @param  receiver             Address to set allowance on once the trade has completed
     * @param  makerToken           Address of makerToken, the token to receive
     * @param  takerToken           Address of takerToken, the token to pay
     * @param  requestedFillAmount  Amount of takerToken being paid
     * @param  orderData            Arbitrary bytes data for any information to pass to the exchange
     * @return                      The amount of makerToken received
     */
    function exchange(
        address tradeOriginator,
        address receiver,
        address makerToken,
        address takerToken,
        uint256 requestedFillAmount,
        bytes calldata orderData
    )
        external
        returns (uint256)
    {
        // prepare the exchange
        (LibOrder.Order memory order, bytes memory signature) = parseSignedOrder(
            orderData,
            makerToken,
            takerToken
        );

        // make sure that the exchange can take the tokens from this contract
        takerToken.ensureAllowance(
            ZERO_EX_TOKEN_PROXY,
            requestedFillAmount
        );

        // execute fill order
        IExchange v3Exchange = IExchange(ZERO_EX_EXCHANGE);
        LibFillResults.FillResults memory fill = v3Exchange.fillOrKillOrder(
            order,
            requestedFillAmount,
            signature
        );

        // set allowance
        makerToken.ensureAllowance(receiver, fill.makerAssetFilledAmount);

        // return excess ETH to the trade originator in the event of protocol fee overpayment
        uint256 protocolFeeRefund = WETH.balanceOf(address(this));
        if (makerToken == address(WETH)) {
            protocolFeeRefund = protocolFeeRefund.sub(fill.makerAssetFilledAmount);
        }
        if (protocolFeeRefund != 0) {
            WETH.withdraw(protocolFeeRefund);
            address(uint160(tradeOriginator)).transfer(protocolFeeRefund);
        }

        return fill.makerAssetFilledAmount;
    }

    /**
     * Get amount of takerToken required to buy a certain amount of makerToken for a given trade.
     * Should match the takerToken amount used in exchangeForAmount. If the order cannot provide
     * exactly desiredMakerToken, then it must return the price to buy the minimum amount greater
     * than desiredMakerToken
     *
     * @param  makerToken         Address of makerToken, the token to receive
     * @param  takerToken         Address of takerToken, the token to pay
     * @param  desiredMakerToken  Amount of makerToken requested
     * @param  orderData          Arbitrary bytes data for any information to pass to the exchange
     * @return                    Amount of takerToken the needed to complete the transaction
     */
    function getExchangeCost(
        address makerToken,
        address takerToken,
        uint256 desiredMakerToken,
        bytes calldata orderData
    )
        external
        view
        returns (uint256)
    {
        (LibOrder.Order memory order, ) = parseSignedOrder(
            orderData,
            makerToken,
            takerToken
        );

        return MathHelpers.getPartialAmountRoundedUp(
            order.takerAssetAmount,
            order.makerAssetAmount,
            desiredMakerToken
        );
    }

    /**
     * Maximum amount of makerToken that can be bought for a given order.
     *
     * @param  makerToken         Address of makerToken, the token to receive
     * @param  takerToken         Address of takerToken, the token to pay
     * @param  orderData          Arbitrary bytes data for any information to pass to the exchange
     */
    function getMaxMakerAmount(
        address makerToken,
        address takerToken,
        bytes calldata orderData
    )
        external
        view
        returns (uint256)
    {
        (LibOrder.Order memory order, ) = parseSignedOrder(
            orderData,
            makerToken,
            takerToken
        );
        IExchange v3Exchange = IExchange(ZERO_EX_EXCHANGE);
        LibOrder.OrderInfo memory orderInfo = v3Exchange.getOrderInfo(order);

        if (orderInfo.orderStatus != LibOrder.OrderStatus.FILLABLE) {
            return 0;
        }

        uint256 remainingTakerAssetAmount =
            order.takerAssetAmount.sub(orderInfo.orderTakerAssetFilledAmount);

        return MathHelpers.getPartialAmount(
            remainingTakerAssetAmount,
            order.takerAssetAmount,
            order.makerAssetAmount
        );
    }

    /**
     * Allow the 0x protocol fee collector to take WETH from this contract
     */
    function approveProtocolFeeCollector() external {
        IExchange v3Exchange = IExchange(ZERO_EX_EXCHANGE);
        address protocolFeeCollector = v3Exchange.protocolFeeCollector();
        if (protocolFeeCollector != address(0)) {
            WETH.approve(
                protocolFeeCollector,
                MathHelpers.maxUint256()
            );
        }
    }

    // ============ Private Functions ============

    /**
     * Parse the order and signature from orderData
     *
     * @param  orderData          Arbitrary bytes data for any information to pass to the exchange
     * @param  makerToken         Address of makerToken, the token to receive
     * @param  takerToken         Address of takerToken, the token to pay
     */
    function parseSignedOrder(
        bytes memory orderData,
        address makerToken,
        address takerToken
    )
        private
        pure
        returns (LibOrder.Order memory, bytes memory)
    {
        (LibOrder.Order memory order, bytes memory signature) = abi.decode(
            orderData,
            (LibOrder.Order, bytes)
        );
        order.makerAssetData = overwriteAssetDataTokenAddress(order.makerAssetData, makerToken);
        order.takerAssetData = overwriteAssetDataTokenAddress(order.takerAssetData, takerToken);

        return (order, signature);
    }

    /**
     * Overwrite the asset data token address with the provided token address
     *
     * @param  assetData          Asset data to overwrite
     * @param  token              Token address to write into the asset data
     */
    function overwriteAssetDataTokenAddress(
        bytes memory assetData,
        address token
    )
        private
        pure
        returns (bytes memory)
    {
        bytes4 assetProxyId = assetData.readBytes4(0);
        require(
            assetProxyId == ERC20_PROXY_ID || assetProxyId == ERC20_BRIDGE_PROXY_ID,
            "ZeroExV3ExchangeWrapper#overwriteAssetDataTokenAddress: invalid asset proxy id"
        );

        bytes memory newAssetData = assetData;
        newAssetData.writeAddress(16, token);
        return newAssetData;
    }
}
/**
 * @title ZeroExV3ExchangeWrapper
 * @author dYdX
 *
 * dYdX ExchangeWrapper to interface with 0x Version 3
 */
function()
    external
    payable
{
    require(
        msg.sender == address(WETH),
        "Cannot receive ETH"
    );
}

/**
 * Fallback function. Disallows ether to be sent to this contract without data except when
 * unwrapping WETH.
 */