feat: ExchangeProxy FillQuoteTransformer bridge direct (#2608)
* Detect Bridge orders and fill direct * Mark as external for try/catch * Initial tests * discuss: Continue if protocol fee insufficient * Emit ProtocolFeeUnfunded * put the clamps on taker balance * feat: GST free and optimize * fix: low level GST free call * fix: review feedback * remove unused return struct
This commit is contained in:
		@@ -31,6 +31,10 @@
 | 
			
		||||
            {
 | 
			
		||||
                "note": "Introduce fill `TransformERC20` feature.",
 | 
			
		||||
                "pr": 2545
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "note": "Fill Bridges directly in `FillQuoteTransformer`.",
 | 
			
		||||
                "pr": 2608
 | 
			
		||||
            }
 | 
			
		||||
        ]
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										46
									
								
								contracts/zero-ex/contracts/src/fixins/FixinGasToken.sol
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								contracts/zero-ex/contracts/src/fixins/FixinGasToken.sol
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,46 @@
 | 
			
		||||
/*
 | 
			
		||||
 | 
			
		||||
  Copyright 2020 ZeroEx Intl.
 | 
			
		||||
 | 
			
		||||
  Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
  you may not use this file except in compliance with the License.
 | 
			
		||||
  You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
    http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
  Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
  distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
  See the License for the specific language governing permissions and
 | 
			
		||||
  limitations under the License.
 | 
			
		||||
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
pragma solidity ^0.6.5;
 | 
			
		||||
 | 
			
		||||
import "../vendor/v3/IGasToken.sol";
 | 
			
		||||
 | 
			
		||||
contract FixinGasToken
 | 
			
		||||
{
 | 
			
		||||
    /// @dev Mainnet address of the GST2 contract
 | 
			
		||||
    address constant private GST_ADDRESS = 0x0000000000b3F879cb30FE243b4Dfee438691c04;
 | 
			
		||||
    /// @dev Mainnet address of the GST Collector
 | 
			
		||||
    address constant private GST_COLLECTOR_ADDRESS = 0x000000D3b08566BE75A6DB803C03C85C0c1c5B96;
 | 
			
		||||
 | 
			
		||||
    /// @dev Frees gas tokens using the balance of `from`. Amount freed is based
 | 
			
		||||
    ///     on the gas consumed in the function
 | 
			
		||||
    modifier freesGasTokensFromCollector() {
 | 
			
		||||
        uint256 gasBefore = gasleft();
 | 
			
		||||
        _;
 | 
			
		||||
        // (gasUsed + FREE_BASE) / (2 * REIMBURSE - FREE_TOKEN)
 | 
			
		||||
        //            14154             24000        6870
 | 
			
		||||
        uint256 value = (gasBefore - gasleft() + 14154) / 41130;
 | 
			
		||||
        GST_ADDRESS.call(
 | 
			
		||||
            abi.encodeWithSelector(
 | 
			
		||||
                IGasToken(address(0)).freeFromUpTo.selector,
 | 
			
		||||
                GST_COLLECTOR_ADDRESS,
 | 
			
		||||
                value
 | 
			
		||||
            )
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -27,18 +27,23 @@ import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
 | 
			
		||||
import "@0x/contracts-utils/contracts/src/v06/LibMathV06.sol";
 | 
			
		||||
import "../errors/LibTransformERC20RichErrors.sol";
 | 
			
		||||
import "../vendor/v3/IExchange.sol";
 | 
			
		||||
import "../vendor/v3/IERC20Bridge.sol";
 | 
			
		||||
import "./Transformer.sol";
 | 
			
		||||
import "./LibERC20Transformer.sol";
 | 
			
		||||
import "../fixins/FixinGasToken.sol";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/// @dev A transformer that fills an ERC20 market sell/buy quote.
 | 
			
		||||
///      This transformer shortcuts bridge orders and fills them directly
 | 
			
		||||
contract FillQuoteTransformer is
 | 
			
		||||
    Transformer
 | 
			
		||||
    Transformer,
 | 
			
		||||
    FixinGasToken
 | 
			
		||||
{
 | 
			
		||||
    using LibERC20TokenV06 for IERC20TokenV06;
 | 
			
		||||
    using LibERC20Transformer for IERC20TokenV06;
 | 
			
		||||
    using LibSafeMathV06 for uint256;
 | 
			
		||||
    using LibRichErrorsV06 for bytes;
 | 
			
		||||
    using LibBytesV06 for bytes;
 | 
			
		||||
 | 
			
		||||
    /// @dev Whether we are performing a market sell or buy.
 | 
			
		||||
    enum Side {
 | 
			
		||||
@@ -81,8 +86,29 @@ contract FillQuoteTransformer is
 | 
			
		||||
        uint256 protocolFeePaid;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// @dev Intermediate state variables to get around stack limits.
 | 
			
		||||
    struct FillState {
 | 
			
		||||
        uint256 ethRemaining;
 | 
			
		||||
        uint256 boughtAmount;
 | 
			
		||||
        uint256 soldAmount;
 | 
			
		||||
        uint256 protocolFee;
 | 
			
		||||
        uint256 takerTokenBalanceRemaining;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// @dev Emitted when a trade is skipped due to a lack of funds
 | 
			
		||||
    ///      to pay the 0x Protocol fee.
 | 
			
		||||
    /// @param ethBalance The current eth balance.
 | 
			
		||||
    /// @param ethNeeded The current eth balance required to pay
 | 
			
		||||
    ///        the protocol fee.
 | 
			
		||||
    event ProtocolFeeUnfunded(
 | 
			
		||||
        uint256 ethBalance,
 | 
			
		||||
        uint256 ethNeeded
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    /// @dev The Exchange ERC20Proxy ID.
 | 
			
		||||
    bytes4 private constant ERC20_ASSET_PROXY_ID = 0xf47261b0;
 | 
			
		||||
    /// @dev The Exchange ERC20BridgeProxy ID.
 | 
			
		||||
    bytes4 private constant ERC20_BRIDGE_PROXY_ID = 0xdc1600f3;
 | 
			
		||||
    /// @dev Maximum uint256 value.
 | 
			
		||||
    uint256 private constant MAX_UINT256 = uint256(-1);
 | 
			
		||||
 | 
			
		||||
@@ -113,9 +139,11 @@ contract FillQuoteTransformer is
 | 
			
		||||
    )
 | 
			
		||||
        external
 | 
			
		||||
        override
 | 
			
		||||
        freesGasTokensFromCollector
 | 
			
		||||
        returns (bytes4 success)
 | 
			
		||||
    {
 | 
			
		||||
        TransformData memory data = abi.decode(data_, (TransformData));
 | 
			
		||||
        FillState memory state;
 | 
			
		||||
 | 
			
		||||
        // Validate data fields.
 | 
			
		||||
        if (data.sellToken.isTokenETH() || data.buyToken.isTokenETH()) {
 | 
			
		||||
@@ -131,43 +159,35 @@ contract FillQuoteTransformer is
 | 
			
		||||
            ).rrevert();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        state.takerTokenBalanceRemaining = data.sellToken.getTokenBalanceOf(address(this));
 | 
			
		||||
        if (data.side == Side.Sell && data.fillAmount == MAX_UINT256) {
 | 
			
		||||
            // If `sellAmount == -1 then we are selling
 | 
			
		||||
            // the entire balance of `sellToken`. This is useful in cases where
 | 
			
		||||
            // the exact sell amount is not exactly known in advance, like when
 | 
			
		||||
            // unwrapping Chai/cUSDC/cDAI.
 | 
			
		||||
            data.fillAmount = data.sellToken.getTokenBalanceOf(address(this));
 | 
			
		||||
            data.fillAmount = state.takerTokenBalanceRemaining;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Approve the ERC20 proxy to spend `sellToken`.
 | 
			
		||||
        data.sellToken.approveIfBelow(erc20Proxy, data.fillAmount);
 | 
			
		||||
 | 
			
		||||
        // Fill the orders.
 | 
			
		||||
        uint256 singleProtocolFee = exchange.protocolFeeMultiplier().safeMul(tx.gasprice);
 | 
			
		||||
        uint256 ethRemaining = address(this).balance;
 | 
			
		||||
        uint256 boughtAmount = 0;
 | 
			
		||||
        uint256 soldAmount = 0;
 | 
			
		||||
        state.protocolFee = exchange.protocolFeeMultiplier().safeMul(tx.gasprice);
 | 
			
		||||
        state.ethRemaining = address(this).balance;
 | 
			
		||||
        for (uint256 i = 0; i < data.orders.length; ++i) {
 | 
			
		||||
            // Check if we've hit our targets.
 | 
			
		||||
            if (data.side == Side.Sell) {
 | 
			
		||||
                // Market sell check.
 | 
			
		||||
                if (soldAmount >= data.fillAmount) {
 | 
			
		||||
                if (state.soldAmount >= data.fillAmount) {
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                // Market buy check.
 | 
			
		||||
                if (boughtAmount >= data.fillAmount) {
 | 
			
		||||
                if (state.boughtAmount >= data.fillAmount) {
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Ensure we have enough ETH to cover the protocol fee.
 | 
			
		||||
            if (ethRemaining < singleProtocolFee) {
 | 
			
		||||
                LibTransformERC20RichErrors
 | 
			
		||||
                    .InsufficientProtocolFeeError(ethRemaining, singleProtocolFee)
 | 
			
		||||
                    .rrevert();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Fill the order.
 | 
			
		||||
            FillOrderResults memory results;
 | 
			
		||||
            if (data.side == Side.Sell) {
 | 
			
		||||
@@ -177,12 +197,12 @@ contract FillQuoteTransformer is
 | 
			
		||||
                    data.sellToken,
 | 
			
		||||
                    data.orders[i],
 | 
			
		||||
                    data.signatures[i],
 | 
			
		||||
                    data.fillAmount.safeSub(soldAmount).min256(
 | 
			
		||||
                    data.fillAmount.safeSub(state.soldAmount).min256(
 | 
			
		||||
                        data.maxOrderFillAmounts.length > i
 | 
			
		||||
                        ? data.maxOrderFillAmounts[i]
 | 
			
		||||
                        : MAX_UINT256
 | 
			
		||||
                    ),
 | 
			
		||||
                    singleProtocolFee
 | 
			
		||||
                    state
 | 
			
		||||
                );
 | 
			
		||||
            } else {
 | 
			
		||||
                // Market buy.
 | 
			
		||||
@@ -191,39 +211,40 @@ contract FillQuoteTransformer is
 | 
			
		||||
                    data.sellToken,
 | 
			
		||||
                    data.orders[i],
 | 
			
		||||
                    data.signatures[i],
 | 
			
		||||
                    data.fillAmount.safeSub(boughtAmount).min256(
 | 
			
		||||
                    data.fillAmount.safeSub(state.boughtAmount).min256(
 | 
			
		||||
                        data.maxOrderFillAmounts.length > i
 | 
			
		||||
                        ? data.maxOrderFillAmounts[i]
 | 
			
		||||
                        : MAX_UINT256
 | 
			
		||||
                    ),
 | 
			
		||||
                    singleProtocolFee
 | 
			
		||||
                    state
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Accumulate totals.
 | 
			
		||||
            soldAmount = soldAmount.safeAdd(results.takerTokenSoldAmount);
 | 
			
		||||
            boughtAmount = boughtAmount.safeAdd(results.makerTokenBoughtAmount);
 | 
			
		||||
            ethRemaining = ethRemaining.safeSub(results.protocolFeePaid);
 | 
			
		||||
            state.soldAmount = state.soldAmount.safeAdd(results.takerTokenSoldAmount);
 | 
			
		||||
            state.boughtAmount = state.boughtAmount.safeAdd(results.makerTokenBoughtAmount);
 | 
			
		||||
            state.ethRemaining = state.ethRemaining.safeSub(results.protocolFeePaid);
 | 
			
		||||
            state.takerTokenBalanceRemaining = state.takerTokenBalanceRemaining.safeSub(results.takerTokenSoldAmount);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Ensure we hit our targets.
 | 
			
		||||
        if (data.side == Side.Sell) {
 | 
			
		||||
            // Market sell check.
 | 
			
		||||
            if (soldAmount < data.fillAmount) {
 | 
			
		||||
            if (state.soldAmount < data.fillAmount) {
 | 
			
		||||
                LibTransformERC20RichErrors
 | 
			
		||||
                    .IncompleteFillSellQuoteError(
 | 
			
		||||
                        address(data.sellToken),
 | 
			
		||||
                        soldAmount,
 | 
			
		||||
                        state.soldAmount,
 | 
			
		||||
                        data.fillAmount
 | 
			
		||||
                    ).rrevert();
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            // Market buy check.
 | 
			
		||||
            if (boughtAmount < data.fillAmount) {
 | 
			
		||||
            if (state.boughtAmount < data.fillAmount) {
 | 
			
		||||
                LibTransformERC20RichErrors
 | 
			
		||||
                    .IncompleteFillBuyQuoteError(
 | 
			
		||||
                        address(data.buyToken),
 | 
			
		||||
                        boughtAmount,
 | 
			
		||||
                        state.boughtAmount,
 | 
			
		||||
                        data.fillAmount
 | 
			
		||||
                    ).rrevert();
 | 
			
		||||
            }
 | 
			
		||||
@@ -237,14 +258,14 @@ contract FillQuoteTransformer is
 | 
			
		||||
    /// @param order The order to fill.
 | 
			
		||||
    /// @param signature The signature for `order`.
 | 
			
		||||
    /// @param sellAmount Amount of taker token to sell.
 | 
			
		||||
    /// @param protocolFee The protocol fee needed to fill `order`.
 | 
			
		||||
    /// @param state Intermediate state variables to get around stack limits.
 | 
			
		||||
    function _sellToOrder(
 | 
			
		||||
        IERC20TokenV06 makerToken,
 | 
			
		||||
        IERC20TokenV06 takerToken,
 | 
			
		||||
        IExchange.Order memory order,
 | 
			
		||||
        bytes memory signature,
 | 
			
		||||
        uint256 sellAmount,
 | 
			
		||||
        uint256 protocolFee
 | 
			
		||||
        FillState memory state
 | 
			
		||||
    )
 | 
			
		||||
        private
 | 
			
		||||
        returns (FillOrderResults memory results)
 | 
			
		||||
@@ -281,18 +302,12 @@ contract FillQuoteTransformer is
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Clamp fill amount to order size.
 | 
			
		||||
        takerTokenFillAmount = LibSafeMathV06.min256(
 | 
			
		||||
            takerTokenFillAmount,
 | 
			
		||||
            order.takerAssetAmount
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        // Perform the fill.
 | 
			
		||||
        return _fillOrder(
 | 
			
		||||
            order,
 | 
			
		||||
            signature,
 | 
			
		||||
            takerTokenFillAmount,
 | 
			
		||||
            protocolFee,
 | 
			
		||||
            state,
 | 
			
		||||
            makerToken,
 | 
			
		||||
            takerFeeToken == takerToken
 | 
			
		||||
        );
 | 
			
		||||
@@ -304,14 +319,14 @@ contract FillQuoteTransformer is
 | 
			
		||||
    /// @param order The order to fill.
 | 
			
		||||
    /// @param signature The signature for `order`.
 | 
			
		||||
    /// @param buyAmount Amount of maker token to buy.
 | 
			
		||||
    /// @param protocolFee The protocol fee needed to fill `order`.
 | 
			
		||||
    /// @param state Intermediate state variables to get around stack limits.
 | 
			
		||||
    function _buyFromOrder(
 | 
			
		||||
        IERC20TokenV06 makerToken,
 | 
			
		||||
        IERC20TokenV06 takerToken,
 | 
			
		||||
        IExchange.Order memory order,
 | 
			
		||||
        bytes memory signature,
 | 
			
		||||
        uint256 buyAmount,
 | 
			
		||||
        uint256 protocolFee
 | 
			
		||||
        FillState memory state
 | 
			
		||||
    )
 | 
			
		||||
        private
 | 
			
		||||
        returns (FillOrderResults memory results)
 | 
			
		||||
@@ -351,18 +366,12 @@ contract FillQuoteTransformer is
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Clamp to order size.
 | 
			
		||||
        takerTokenFillAmount = LibSafeMathV06.min256(
 | 
			
		||||
            order.takerAssetAmount,
 | 
			
		||||
            takerTokenFillAmount
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        // Perform the fill.
 | 
			
		||||
        return _fillOrder(
 | 
			
		||||
            order,
 | 
			
		||||
            signature,
 | 
			
		||||
            takerTokenFillAmount,
 | 
			
		||||
            protocolFee,
 | 
			
		||||
            state,
 | 
			
		||||
            makerToken,
 | 
			
		||||
            takerFeeToken == takerToken
 | 
			
		||||
        );
 | 
			
		||||
@@ -373,7 +382,7 @@ contract FillQuoteTransformer is
 | 
			
		||||
    /// @param order The order to fill.
 | 
			
		||||
    /// @param signature The order signature.
 | 
			
		||||
    /// @param takerAssetFillAmount How much taker asset to fill.
 | 
			
		||||
    /// @param protocolFee The protocol fee needed to fill this order.
 | 
			
		||||
    /// @param state Intermediate state variables to get around stack limits.
 | 
			
		||||
    /// @param makerToken The maker token.
 | 
			
		||||
    /// @param isTakerFeeInTakerToken Whether the taker fee token is the same as the
 | 
			
		||||
    ///        taker token.
 | 
			
		||||
@@ -381,27 +390,64 @@ contract FillQuoteTransformer is
 | 
			
		||||
        IExchange.Order memory order,
 | 
			
		||||
        bytes memory signature,
 | 
			
		||||
        uint256 takerAssetFillAmount,
 | 
			
		||||
        uint256 protocolFee,
 | 
			
		||||
        FillState memory state,
 | 
			
		||||
        IERC20TokenV06 makerToken,
 | 
			
		||||
        bool isTakerFeeInTakerToken
 | 
			
		||||
    )
 | 
			
		||||
        private
 | 
			
		||||
        returns (FillOrderResults memory results)
 | 
			
		||||
    {
 | 
			
		||||
        // Track changes in the maker token balance.
 | 
			
		||||
        uint256 initialMakerTokenBalance = makerToken.balanceOf(address(this));
 | 
			
		||||
        // Clamp to remaining taker asset amount or order size.
 | 
			
		||||
        uint256 availableTakerAssetFillAmount =
 | 
			
		||||
            takerAssetFillAmount.min256(order.takerAssetAmount);
 | 
			
		||||
        availableTakerAssetFillAmount =
 | 
			
		||||
            availableTakerAssetFillAmount.min256(state.takerTokenBalanceRemaining);
 | 
			
		||||
        // If it is a Bridge order we fill this directly
 | 
			
		||||
        // rather than filling via 0x Exchange
 | 
			
		||||
        if (order.makerAssetData.readBytes4(0) == ERC20_BRIDGE_PROXY_ID) {
 | 
			
		||||
            // Calculate the amount (in maker token) we expect to receive
 | 
			
		||||
            // from the bridge
 | 
			
		||||
            uint256 outputTokenAmount = LibMathV06.getPartialAmountFloor(
 | 
			
		||||
                availableTakerAssetFillAmount,
 | 
			
		||||
                order.takerAssetAmount,
 | 
			
		||||
                order.makerAssetAmount
 | 
			
		||||
            );
 | 
			
		||||
            (bool success, bytes memory data) = address(_implementation).delegatecall(
 | 
			
		||||
                abi.encodeWithSelector(
 | 
			
		||||
                    this.fillBridgeOrder.selector,
 | 
			
		||||
                    order.makerAddress,
 | 
			
		||||
                    order.makerAssetData,
 | 
			
		||||
                    order.takerAssetData,
 | 
			
		||||
                    availableTakerAssetFillAmount,
 | 
			
		||||
                    outputTokenAmount
 | 
			
		||||
                )
 | 
			
		||||
            );
 | 
			
		||||
            // Swallow failures, leaving all results as zero.
 | 
			
		||||
            // TransformERC20 asserts the overall price is as expected. It is possible
 | 
			
		||||
            // a subsequent fill can net out at the expected price so we do not assert
 | 
			
		||||
            // the trade balance
 | 
			
		||||
            if (success) {
 | 
			
		||||
                results.makerTokenBoughtAmount = makerToken
 | 
			
		||||
                    .balanceOf(address(this))
 | 
			
		||||
                    .safeSub(state.boughtAmount);
 | 
			
		||||
                results.takerTokenSoldAmount = availableTakerAssetFillAmount;
 | 
			
		||||
                // protocol fee paid remains 0
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            // Emit an event if we do not have sufficient ETH to cover the protocol fee.
 | 
			
		||||
            if (state.ethRemaining < state.protocolFee) {
 | 
			
		||||
                emit ProtocolFeeUnfunded(state.ethRemaining, state.protocolFee);
 | 
			
		||||
                return results;
 | 
			
		||||
            }
 | 
			
		||||
            try
 | 
			
		||||
                exchange.fillOrder
 | 
			
		||||
                {value: protocolFee}
 | 
			
		||||
                (order, takerAssetFillAmount, signature)
 | 
			
		||||
                    {value: state.protocolFee}
 | 
			
		||||
                    (order, availableTakerAssetFillAmount, signature)
 | 
			
		||||
                returns (IExchange.FillResults memory fillResults)
 | 
			
		||||
            {
 | 
			
		||||
            // Update maker quantity based on changes in token balances.
 | 
			
		||||
            results.makerTokenBoughtAmount = makerToken.balanceOf(address(this))
 | 
			
		||||
                .safeSub(initialMakerTokenBalance);
 | 
			
		||||
            // We can trust the other fill result quantities.
 | 
			
		||||
            results.protocolFeePaid = fillResults.protocolFeePaid;
 | 
			
		||||
                results.makerTokenBoughtAmount = fillResults.makerAssetFilledAmount;
 | 
			
		||||
                results.takerTokenSoldAmount = fillResults.takerAssetFilledAmount;
 | 
			
		||||
                results.protocolFeePaid = fillResults.protocolFeePaid;
 | 
			
		||||
                // If the taker fee is payable in the taker asset, include the
 | 
			
		||||
                // taker fee in the total amount sold.
 | 
			
		||||
                if (isTakerFeeInTakerToken) {
 | 
			
		||||
@@ -412,6 +458,47 @@ contract FillQuoteTransformer is
 | 
			
		||||
                // Swallow failures, leaving all results as zero.
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// @dev Attempt to fill an ERC20 Bridge order. If the fill reverts,
 | 
			
		||||
    ///      or the amount filled was not sufficient this reverts.
 | 
			
		||||
    /// @param makerAddress The address of the maker.
 | 
			
		||||
    /// @param makerAssetData The encoded ERC20BridgeProxy asset data.
 | 
			
		||||
    /// @param takerAssetData The encoded ERC20 asset data.
 | 
			
		||||
    /// @param inputTokenAmount How much taker asset to fill clamped to the available balance.
 | 
			
		||||
    /// @param outputTokenAmount How much maker asset to receive.
 | 
			
		||||
    function fillBridgeOrder(
 | 
			
		||||
        address makerAddress,
 | 
			
		||||
        bytes calldata makerAssetData,
 | 
			
		||||
        bytes calldata takerAssetData,
 | 
			
		||||
        uint256 inputTokenAmount,
 | 
			
		||||
        uint256 outputTokenAmount
 | 
			
		||||
    )
 | 
			
		||||
        external
 | 
			
		||||
    {
 | 
			
		||||
        // Track changes in the maker token balance.
 | 
			
		||||
        (
 | 
			
		||||
            address tokenAddress,
 | 
			
		||||
            address bridgeAddress,
 | 
			
		||||
            bytes memory bridgeData
 | 
			
		||||
        ) = abi.decode(
 | 
			
		||||
            makerAssetData.sliceDestructive(4, makerAssetData.length),
 | 
			
		||||
            (address, address, bytes)
 | 
			
		||||
        );
 | 
			
		||||
        require(bridgeAddress != address(this), "INVALID_BRIDGE_ADDRESS");
 | 
			
		||||
        // Transfer the tokens to the bridge to perform the work
 | 
			
		||||
        _getTokenFromERC20AssetData(takerAssetData).compatTransfer(
 | 
			
		||||
            bridgeAddress,
 | 
			
		||||
            inputTokenAmount
 | 
			
		||||
        );
 | 
			
		||||
        IERC20Bridge(bridgeAddress).bridgeTransferFrom(
 | 
			
		||||
            tokenAddress,
 | 
			
		||||
            makerAddress,
 | 
			
		||||
            address(this),
 | 
			
		||||
            outputTokenAmount, // amount to transfer back from the bridge
 | 
			
		||||
            bridgeData
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// @dev Extract the token from plain ERC20 asset data.
 | 
			
		||||
    ///      If the asset-data is empty, a zero token address will be returned.
 | 
			
		||||
 
 | 
			
		||||
@@ -33,7 +33,7 @@ abstract contract Transformer is
 | 
			
		||||
    /// @dev The address of the deployer.
 | 
			
		||||
    address public immutable deployer;
 | 
			
		||||
    /// @dev The original address of this contract.
 | 
			
		||||
    address private immutable _implementation;
 | 
			
		||||
    address internal immutable _implementation;
 | 
			
		||||
 | 
			
		||||
    /// @dev Create this contract.
 | 
			
		||||
    constructor() public {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										55
									
								
								contracts/zero-ex/contracts/src/vendor/v3/IERC20Bridge.sol
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								contracts/zero-ex/contracts/src/vendor/v3/IERC20Bridge.sol
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,55 @@
 | 
			
		||||
/*
 | 
			
		||||
 | 
			
		||||
  Copyright 2020 ZeroEx Intl.
 | 
			
		||||
 | 
			
		||||
  Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
  you may not use this file except in compliance with the License.
 | 
			
		||||
  You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
    http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
  Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
  distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
  See the License for the specific language governing permissions and
 | 
			
		||||
  limitations under the License.
 | 
			
		||||
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
pragma solidity ^0.6.5;
 | 
			
		||||
 | 
			
		||||
interface IERC20Bridge {
 | 
			
		||||
 | 
			
		||||
    /// @dev Emitted when a trade occurs.
 | 
			
		||||
    /// @param inputToken The token the bridge is converting from.
 | 
			
		||||
    /// @param outputToken The token the bridge is converting to.
 | 
			
		||||
    /// @param inputTokenAmount Amount of input token.
 | 
			
		||||
    /// @param outputTokenAmount Amount of output token.
 | 
			
		||||
    /// @param from The `from` address in `bridgeTransferFrom()`
 | 
			
		||||
    /// @param to The `to` address in `bridgeTransferFrom()`
 | 
			
		||||
    event ERC20BridgeTransfer(
 | 
			
		||||
        address inputToken,
 | 
			
		||||
        address outputToken,
 | 
			
		||||
        uint256 inputTokenAmount,
 | 
			
		||||
        uint256 outputTokenAmount,
 | 
			
		||||
        address from,
 | 
			
		||||
        address to
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    /// @dev Transfers `amount` of the ERC20 `tokenAddress` from `from` to `to`.
 | 
			
		||||
    /// @param tokenAddress The address of the ERC20 token to transfer.
 | 
			
		||||
    /// @param from Address to transfer asset from.
 | 
			
		||||
    /// @param to Address to transfer asset to.
 | 
			
		||||
    /// @param amount Amount of asset to transfer.
 | 
			
		||||
    /// @param bridgeData Arbitrary asset data needed by the bridge contract.
 | 
			
		||||
    /// @return success The magic bytes `0xdc1600f3` if successful.
 | 
			
		||||
    function bridgeTransferFrom(
 | 
			
		||||
        address tokenAddress,
 | 
			
		||||
        address from,
 | 
			
		||||
        address to,
 | 
			
		||||
        uint256 amount,
 | 
			
		||||
        bytes calldata bridgeData
 | 
			
		||||
    )
 | 
			
		||||
        external
 | 
			
		||||
        returns (bytes4 success);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										37
									
								
								contracts/zero-ex/contracts/src/vendor/v3/IGasToken.sol
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								contracts/zero-ex/contracts/src/vendor/v3/IGasToken.sol
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,37 @@
 | 
			
		||||
/*
 | 
			
		||||
 | 
			
		||||
  Copyright 2020 ZeroEx Intl.
 | 
			
		||||
 | 
			
		||||
  Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
  you may not use this file except in compliance with the License.
 | 
			
		||||
  You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
    http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
  Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
  distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
  See the License for the specific language governing permissions and
 | 
			
		||||
  limitations under the License.
 | 
			
		||||
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
pragma solidity ^0.6.5;
 | 
			
		||||
 | 
			
		||||
interface IGasToken {
 | 
			
		||||
 | 
			
		||||
    /// @dev Frees up to `value` sub-tokens
 | 
			
		||||
    /// @param value The amount of tokens to free
 | 
			
		||||
    /// @return freed How many tokens were freed
 | 
			
		||||
    function freeUpTo(uint256 value) external returns (uint256 freed);
 | 
			
		||||
 | 
			
		||||
    /// @dev Frees up to `value` sub-tokens owned by `from`
 | 
			
		||||
    /// @param from The owner of tokens to spend
 | 
			
		||||
    /// @param value The amount of tokens to free
 | 
			
		||||
    /// @return freed How many tokens were freed
 | 
			
		||||
    function freeFromUpTo(address from, uint256 value) external returns (uint256 freed);
 | 
			
		||||
 | 
			
		||||
    /// @dev Mints `value` amount of tokens
 | 
			
		||||
    /// @param value The amount of tokens to mint
 | 
			
		||||
    function mint(uint256 value) external;
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,65 @@
 | 
			
		||||
/*
 | 
			
		||||
 | 
			
		||||
  Copyright 2020 ZeroEx Intl.
 | 
			
		||||
 | 
			
		||||
  Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
  you may not use this file except in compliance with the License.
 | 
			
		||||
  You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
    http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
  Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
  distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
  See the License for the specific language governing permissions and
 | 
			
		||||
  limitations under the License.
 | 
			
		||||
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
pragma solidity ^0.6.5;
 | 
			
		||||
pragma experimental ABIEncoderV2;
 | 
			
		||||
 | 
			
		||||
import "@0x/contracts-utils/contracts/src/v06/LibMathV06.sol";
 | 
			
		||||
import "../src/vendor/v3/IERC20Bridge.sol";
 | 
			
		||||
import "./TestMintableERC20Token.sol";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
contract TestFillQuoteTransformerBridge {
 | 
			
		||||
 | 
			
		||||
    struct FillBehavior {
 | 
			
		||||
        // Scaling for maker assets minted, in 1e18.
 | 
			
		||||
        uint256 makerAssetMintRatio;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bytes4 private constant ERC20_BRIDGE_PROXY_ID = 0xdc1600f3;
 | 
			
		||||
 | 
			
		||||
    function bridgeTransferFrom(
 | 
			
		||||
        address tokenAddress,
 | 
			
		||||
        address from,
 | 
			
		||||
        address to,
 | 
			
		||||
        uint256 amount,
 | 
			
		||||
        bytes calldata bridgeData
 | 
			
		||||
    )
 | 
			
		||||
        external
 | 
			
		||||
        returns (bytes4 success)
 | 
			
		||||
    {
 | 
			
		||||
        FillBehavior memory behavior = abi.decode(bridgeData, (FillBehavior));
 | 
			
		||||
        TestMintableERC20Token(tokenAddress).mint(
 | 
			
		||||
          to,
 | 
			
		||||
          LibMathV06.getPartialAmountFloor(
 | 
			
		||||
              behavior.makerAssetMintRatio,
 | 
			
		||||
              1e18,
 | 
			
		||||
              amount
 | 
			
		||||
          )
 | 
			
		||||
        );
 | 
			
		||||
        return ERC20_BRIDGE_PROXY_ID;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function encodeBehaviorData(FillBehavior calldata behavior)
 | 
			
		||||
        external
 | 
			
		||||
        pure
 | 
			
		||||
        returns (bytes memory encoded)
 | 
			
		||||
    {
 | 
			
		||||
        return abi.encode(behavior);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -35,12 +35,13 @@
 | 
			
		||||
        "lint-contracts": "#solhint -c ../.solhint.json contracts/**/**/**/**/*.sol",
 | 
			
		||||
        "compile:truffle": "truffle compile",
 | 
			
		||||
        "docs:md": "ts-doc-gen --sourceDir='$PROJECT_FILES' --output=$MD_FILE_DIR --fileExtension=mdx --tsconfig=./typedoc-tsconfig.json",
 | 
			
		||||
        "docs:json": "typedoc --excludePrivate --excludeExternals --excludeProtected --ignoreCompilerErrors --target ES5 --tsconfig typedoc-tsconfig.json --json $JSON_FILE_PATH $PROJECT_FILES"
 | 
			
		||||
        "docs:json": "typedoc --excludePrivate --excludeExternals --excludeProtected --ignoreCompilerErrors --target ES5 --tsconfig typedoc-tsconfig.json --json $JSON_FILE_PATH $PROJECT_FILES",
 | 
			
		||||
        "publish:private": "yarn build && gitpkg publish"
 | 
			
		||||
    },
 | 
			
		||||
    "config": {
 | 
			
		||||
        "publicInterfaceContracts": "ZeroEx,FullMigration,InitialMigration,IFlashWallet,IAllowanceTarget,IERC20Transformer,IOwnable,ISimpleFunctionRegistry,ITokenSpender,ITransformERC20,FillQuoteTransformer,PayTakerTransformer,WethTransformer,Ownable,SimpleFunctionRegistry,TransformERC20,TokenSpender,AffiliateFeeTransformer",
 | 
			
		||||
        "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
 | 
			
		||||
        "abis": "./test/generated-artifacts/@(AffiliateFeeTransformer|AllowanceTarget|Bootstrap|FillQuoteTransformer|FixinCommon|FlashWallet|FullMigration|IAllowanceTarget|IBootstrap|IERC20Transformer|IExchange|IFeature|IFlashWallet|IOwnable|ISimpleFunctionRegistry|ITestSimpleFunctionRegistryFeature|ITokenSpender|ITransformERC20|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC20Transformer|LibMigrate|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibSpenderRichErrors|LibStorage|LibTokenSpenderStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|Ownable|PayTakerTransformer|SimpleFunctionRegistry|TestCallTarget|TestDelegateCaller|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFullMigration|TestInitialMigration|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC20Token|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestTokenSpender|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestWeth|TestWethTransformerHost|TestZeroExFeature|TokenSpender|TransformERC20|Transformer|TransformerDeployer|WethTransformer|ZeroEx).json"
 | 
			
		||||
        "abis": "./test/generated-artifacts/@(AffiliateFeeTransformer|AllowanceTarget|Bootstrap|FillQuoteTransformer|FixinCommon|FixinGasToken|FlashWallet|FullMigration|IAllowanceTarget|IBootstrap|IERC20Bridge|IERC20Transformer|IExchange|IFeature|IFlashWallet|IGasToken|IOwnable|ISimpleFunctionRegistry|ITestSimpleFunctionRegistryFeature|ITokenSpender|ITransformERC20|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC20Transformer|LibMigrate|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibSpenderRichErrors|LibStorage|LibTokenSpenderStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|Ownable|PayTakerTransformer|SimpleFunctionRegistry|TestCallTarget|TestDelegateCaller|TestFillQuoteTransformerBridge|TestFillQuoteTransformerExchange|TestFillQuoteTransformerHost|TestFullMigration|TestInitialMigration|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC20Token|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestTokenSpender|TestTokenSpenderERC20Token|TestTransformERC20|TestTransformerBase|TestTransformerDeployerTransformer|TestTransformerHost|TestWeth|TestWethTransformerHost|TestZeroExFeature|TokenSpender|TransformERC20|Transformer|TransformerDeployer|WethTransformer|ZeroEx).json"
 | 
			
		||||
    },
 | 
			
		||||
    "repository": {
 | 
			
		||||
        "type": "git",
 | 
			
		||||
 
 | 
			
		||||
@@ -10,14 +10,17 @@ import * as AllowanceTarget from '../test/generated-artifacts/AllowanceTarget.js
 | 
			
		||||
import * as Bootstrap from '../test/generated-artifacts/Bootstrap.json';
 | 
			
		||||
import * as FillQuoteTransformer from '../test/generated-artifacts/FillQuoteTransformer.json';
 | 
			
		||||
import * as FixinCommon from '../test/generated-artifacts/FixinCommon.json';
 | 
			
		||||
import * as FixinGasToken from '../test/generated-artifacts/FixinGasToken.json';
 | 
			
		||||
import * as FlashWallet from '../test/generated-artifacts/FlashWallet.json';
 | 
			
		||||
import * as FullMigration from '../test/generated-artifacts/FullMigration.json';
 | 
			
		||||
import * as IAllowanceTarget from '../test/generated-artifacts/IAllowanceTarget.json';
 | 
			
		||||
import * as IBootstrap from '../test/generated-artifacts/IBootstrap.json';
 | 
			
		||||
import * as IERC20Bridge from '../test/generated-artifacts/IERC20Bridge.json';
 | 
			
		||||
import * as IERC20Transformer from '../test/generated-artifacts/IERC20Transformer.json';
 | 
			
		||||
import * as IExchange from '../test/generated-artifacts/IExchange.json';
 | 
			
		||||
import * as IFeature from '../test/generated-artifacts/IFeature.json';
 | 
			
		||||
import * as IFlashWallet from '../test/generated-artifacts/IFlashWallet.json';
 | 
			
		||||
import * as IGasToken from '../test/generated-artifacts/IGasToken.json';
 | 
			
		||||
import * as InitialMigration from '../test/generated-artifacts/InitialMigration.json';
 | 
			
		||||
import * as IOwnable from '../test/generated-artifacts/IOwnable.json';
 | 
			
		||||
import * as ISimpleFunctionRegistry from '../test/generated-artifacts/ISimpleFunctionRegistry.json';
 | 
			
		||||
@@ -45,6 +48,7 @@ import * as PayTakerTransformer from '../test/generated-artifacts/PayTakerTransf
 | 
			
		||||
import * as SimpleFunctionRegistry from '../test/generated-artifacts/SimpleFunctionRegistry.json';
 | 
			
		||||
import * as TestCallTarget from '../test/generated-artifacts/TestCallTarget.json';
 | 
			
		||||
import * as TestDelegateCaller from '../test/generated-artifacts/TestDelegateCaller.json';
 | 
			
		||||
import * as TestFillQuoteTransformerBridge from '../test/generated-artifacts/TestFillQuoteTransformerBridge.json';
 | 
			
		||||
import * as TestFillQuoteTransformerExchange from '../test/generated-artifacts/TestFillQuoteTransformerExchange.json';
 | 
			
		||||
import * as TestFillQuoteTransformerHost from '../test/generated-artifacts/TestFillQuoteTransformerHost.json';
 | 
			
		||||
import * as TestFullMigration from '../test/generated-artifacts/TestFullMigration.json';
 | 
			
		||||
@@ -95,6 +99,7 @@ export const artifacts = {
 | 
			
		||||
    TokenSpender: TokenSpender as ContractArtifact,
 | 
			
		||||
    TransformERC20: TransformERC20 as ContractArtifact,
 | 
			
		||||
    FixinCommon: FixinCommon as ContractArtifact,
 | 
			
		||||
    FixinGasToken: FixinGasToken as ContractArtifact,
 | 
			
		||||
    FullMigration: FullMigration as ContractArtifact,
 | 
			
		||||
    InitialMigration: InitialMigration as ContractArtifact,
 | 
			
		||||
    LibBootstrap: LibBootstrap as ContractArtifact,
 | 
			
		||||
@@ -112,10 +117,13 @@ export const artifacts = {
 | 
			
		||||
    PayTakerTransformer: PayTakerTransformer as ContractArtifact,
 | 
			
		||||
    Transformer: Transformer as ContractArtifact,
 | 
			
		||||
    WethTransformer: WethTransformer as ContractArtifact,
 | 
			
		||||
    IERC20Bridge: IERC20Bridge as ContractArtifact,
 | 
			
		||||
    IExchange: IExchange as ContractArtifact,
 | 
			
		||||
    IGasToken: IGasToken as ContractArtifact,
 | 
			
		||||
    ITestSimpleFunctionRegistryFeature: ITestSimpleFunctionRegistryFeature as ContractArtifact,
 | 
			
		||||
    TestCallTarget: TestCallTarget as ContractArtifact,
 | 
			
		||||
    TestDelegateCaller: TestDelegateCaller as ContractArtifact,
 | 
			
		||||
    TestFillQuoteTransformerBridge: TestFillQuoteTransformerBridge as ContractArtifact,
 | 
			
		||||
    TestFillQuoteTransformerExchange: TestFillQuoteTransformerExchange as ContractArtifact,
 | 
			
		||||
    TestFillQuoteTransformerHost: TestFillQuoteTransformerHost as ContractArtifact,
 | 
			
		||||
    TestFullMigration: TestFullMigration as ContractArtifact,
 | 
			
		||||
 
 | 
			
		||||
@@ -18,6 +18,7 @@ import { BigNumber, hexUtils, ZeroExRevertErrors } from '@0x/utils';
 | 
			
		||||
import * as _ from 'lodash';
 | 
			
		||||
 | 
			
		||||
import { artifacts } from '../artifacts';
 | 
			
		||||
import { TestFillQuoteTransformerBridgeContract } from '../generated-wrappers/test_fill_quote_transformer_bridge';
 | 
			
		||||
import {
 | 
			
		||||
    FillQuoteTransformerContract,
 | 
			
		||||
    TestFillQuoteTransformerExchangeContract,
 | 
			
		||||
@@ -31,6 +32,7 @@ blockchainTests.resets('FillQuoteTransformer', env => {
 | 
			
		||||
    let maker: string;
 | 
			
		||||
    let feeRecipient: string;
 | 
			
		||||
    let exchange: TestFillQuoteTransformerExchangeContract;
 | 
			
		||||
    let bridge: TestFillQuoteTransformerBridgeContract;
 | 
			
		||||
    let transformer: FillQuoteTransformerContract;
 | 
			
		||||
    let host: TestFillQuoteTransformerHostContract;
 | 
			
		||||
    let makerToken: TestMintableERC20TokenContract;
 | 
			
		||||
@@ -64,6 +66,12 @@ blockchainTests.resets('FillQuoteTransformer', env => {
 | 
			
		||||
            },
 | 
			
		||||
            artifacts,
 | 
			
		||||
        );
 | 
			
		||||
        bridge = await TestFillQuoteTransformerBridgeContract.deployFrom0xArtifactAsync(
 | 
			
		||||
            artifacts.TestFillQuoteTransformerBridge,
 | 
			
		||||
            env.provider,
 | 
			
		||||
            env.txDefaults,
 | 
			
		||||
            artifacts,
 | 
			
		||||
        );
 | 
			
		||||
        [makerToken, takerToken, takerFeeToken] = await Promise.all(
 | 
			
		||||
            _.times(3, async () =>
 | 
			
		||||
                TestMintableERC20TokenContract.deployFrom0xArtifactAsync(
 | 
			
		||||
@@ -102,6 +110,19 @@ blockchainTests.resets('FillQuoteTransformer', env => {
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function createBridgeOrder(fields: Partial<Order> = {}, bridgeData: string = encodeBridgeBehavior()): FilledOrder {
 | 
			
		||||
        const order = createOrder(fields);
 | 
			
		||||
        return {
 | 
			
		||||
            ...order,
 | 
			
		||||
            makerAddress: bridge.address,
 | 
			
		||||
            makerAssetData: assetDataUtils.encodeERC20BridgeAssetData(makerToken.address, bridge.address, bridgeData),
 | 
			
		||||
            makerFeeAssetData: NULL_BYTES,
 | 
			
		||||
            takerFeeAssetData: NULL_BYTES,
 | 
			
		||||
            makerFee: ZERO_AMOUNT,
 | 
			
		||||
            takerFee: ZERO_AMOUNT,
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    interface QuoteFillResults {
 | 
			
		||||
        makerAssetBought: BigNumber;
 | 
			
		||||
        takerAssetSpent: BigNumber;
 | 
			
		||||
@@ -244,6 +265,17 @@ blockchainTests.resets('FillQuoteTransformer', env => {
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function encodeBridgeBehavior(makerAssetMintRatio: Numberish = 1.0): string {
 | 
			
		||||
        return hexUtils.slice(
 | 
			
		||||
            bridge
 | 
			
		||||
                .encodeBehaviorData({
 | 
			
		||||
                    makerAssetMintRatio: new BigNumber(makerAssetMintRatio).times('1e18').integerValue(),
 | 
			
		||||
                })
 | 
			
		||||
                .getABIEncodedTransactionData(),
 | 
			
		||||
            4,
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const ERC20_ASSET_PROXY_ID = '0xf47261b0';
 | 
			
		||||
 | 
			
		||||
    describe('sell quotes', () => {
 | 
			
		||||
@@ -436,9 +468,10 @@ blockchainTests.resets('FillQuoteTransformer', env => {
 | 
			
		||||
                )
 | 
			
		||||
                .awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid.minus(1) });
 | 
			
		||||
            return expect(tx).to.revertWith(
 | 
			
		||||
                new ZeroExRevertErrors.TransformERC20.InsufficientProtocolFeeError(
 | 
			
		||||
                    singleProtocolFee.minus(1),
 | 
			
		||||
                    singleProtocolFee,
 | 
			
		||||
                new ZeroExRevertErrors.TransformERC20.IncompleteFillSellQuoteError(
 | 
			
		||||
                    takerToken.address,
 | 
			
		||||
                    getExpectedSellQuoteFillResults([...orders.slice(0, 2)]).takerAssetSpent,
 | 
			
		||||
                    qfr.takerAssetSpent,
 | 
			
		||||
                ),
 | 
			
		||||
            );
 | 
			
		||||
        });
 | 
			
		||||
@@ -707,36 +740,6 @@ blockchainTests.resets('FillQuoteTransformer', env => {
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('succeeds if an order transfers too many maker tokens', async () => {
 | 
			
		||||
            const orders = _.times(2, () => createOrder());
 | 
			
		||||
            // First order will mint its tokens + the maker tokens of the second.
 | 
			
		||||
            const mintScale = orders[1].makerAssetAmount.div(orders[0].makerAssetAmount.minus(1)).plus(1);
 | 
			
		||||
            const signatures = [
 | 
			
		||||
                encodeExchangeBehavior(0, mintScale),
 | 
			
		||||
                ...orders.slice(1).map(() => encodeExchangeBehavior()),
 | 
			
		||||
            ];
 | 
			
		||||
            const qfr = getExpectedBuyQuoteFillResults(orders);
 | 
			
		||||
            await host
 | 
			
		||||
                .executeTransform(
 | 
			
		||||
                    transformer.address,
 | 
			
		||||
                    takerToken.address,
 | 
			
		||||
                    qfr.takerAssetSpent,
 | 
			
		||||
                    encodeTransformData({
 | 
			
		||||
                        orders,
 | 
			
		||||
                        signatures,
 | 
			
		||||
                        side: FillQuoteTransformerSide.Buy,
 | 
			
		||||
                        fillAmount: qfr.makerAssetBought,
 | 
			
		||||
                    }),
 | 
			
		||||
                )
 | 
			
		||||
                .awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid });
 | 
			
		||||
            assertBalances(await getBalancesAsync(host.address), {
 | 
			
		||||
                ...ZERO_BALANCES,
 | 
			
		||||
                makerAssetBalance: orders[0].makerAssetAmount.times(mintScale).integerValue(BigNumber.ROUND_DOWN),
 | 
			
		||||
                takerAssetBalance: orders[1].takerAssetAmount.plus(orders[1].takerFee),
 | 
			
		||||
                protocolFeeBalance: singleProtocolFee,
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('fails to buy more than available in orders', async () => {
 | 
			
		||||
            const orders = _.times(3, () => createOrder());
 | 
			
		||||
            const signatures = orders.map(() => encodeExchangeBehavior());
 | 
			
		||||
@@ -861,4 +864,109 @@ blockchainTests.resets('FillQuoteTransformer', env => {
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    describe('bridge orders', () => {
 | 
			
		||||
        it('can fully sell to a single bridge order quote', async () => {
 | 
			
		||||
            const orders = _.times(1, () => createBridgeOrder());
 | 
			
		||||
            const signatures = orders.map(() => NULL_BYTES);
 | 
			
		||||
            const qfr = getExpectedSellQuoteFillResults(orders);
 | 
			
		||||
            await host
 | 
			
		||||
                .executeTransform(
 | 
			
		||||
                    transformer.address,
 | 
			
		||||
                    takerToken.address,
 | 
			
		||||
                    qfr.takerAssetSpent,
 | 
			
		||||
                    encodeTransformData({
 | 
			
		||||
                        orders,
 | 
			
		||||
                        signatures,
 | 
			
		||||
                    }),
 | 
			
		||||
                )
 | 
			
		||||
                .awaitTransactionSuccessAsync({ value: ZERO_AMOUNT });
 | 
			
		||||
            assertBalances(await getBalancesAsync(host.address), {
 | 
			
		||||
                ...ZERO_BALANCES,
 | 
			
		||||
                makerAssetBalance: qfr.makerAssetBought,
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('can sell to a mix of order quote', async () => {
 | 
			
		||||
            const nativeOrders = [createOrder()];
 | 
			
		||||
            const bridgeOrders = [createBridgeOrder()];
 | 
			
		||||
            const orders = [...nativeOrders, ...bridgeOrders];
 | 
			
		||||
            const signatures = [
 | 
			
		||||
                ...nativeOrders.map(() => encodeExchangeBehavior()), // Valid Signatures
 | 
			
		||||
                ...bridgeOrders.map(() => NULL_BYTES), // Valid Signatures
 | 
			
		||||
            ];
 | 
			
		||||
            const qfr = getExpectedSellQuoteFillResults(orders);
 | 
			
		||||
            await host
 | 
			
		||||
                .executeTransform(
 | 
			
		||||
                    transformer.address,
 | 
			
		||||
                    takerToken.address,
 | 
			
		||||
                    qfr.takerAssetSpent,
 | 
			
		||||
                    encodeTransformData({
 | 
			
		||||
                        orders,
 | 
			
		||||
                        signatures,
 | 
			
		||||
                    }),
 | 
			
		||||
                )
 | 
			
		||||
                .awaitTransactionSuccessAsync({ value: singleProtocolFee.times(nativeOrders.length) });
 | 
			
		||||
            assertBalances(await getBalancesAsync(host.address), {
 | 
			
		||||
                ...ZERO_BALANCES,
 | 
			
		||||
                makerAssetBalance: qfr.makerAssetBought,
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('can attempt to sell to a mix of order quote handling reverts', async () => {
 | 
			
		||||
            const nativeOrders = _.times(3, () => createOrder());
 | 
			
		||||
            const bridgeOrders = [createBridgeOrder()];
 | 
			
		||||
            const orders = [...nativeOrders, ...bridgeOrders];
 | 
			
		||||
            const signatures = [
 | 
			
		||||
                ...nativeOrders.map(() => NULL_BYTES), // Invalid Signatures
 | 
			
		||||
                ...bridgeOrders.map(() => NULL_BYTES), // Valid Signatures
 | 
			
		||||
            ];
 | 
			
		||||
            const qfr = getExpectedSellQuoteFillResults(bridgeOrders);
 | 
			
		||||
            await host
 | 
			
		||||
                .executeTransform(
 | 
			
		||||
                    transformer.address,
 | 
			
		||||
                    takerToken.address,
 | 
			
		||||
                    qfr.takerAssetSpent,
 | 
			
		||||
                    encodeTransformData({
 | 
			
		||||
                        orders,
 | 
			
		||||
                        signatures,
 | 
			
		||||
                    }),
 | 
			
		||||
                )
 | 
			
		||||
                // Single protocol fee as all Native orders will fail
 | 
			
		||||
                .awaitTransactionSuccessAsync({ value: singleProtocolFee });
 | 
			
		||||
            assertBalances(await getBalancesAsync(host.address), {
 | 
			
		||||
                ...ZERO_BALANCES,
 | 
			
		||||
                makerAssetBalance: qfr.makerAssetBought,
 | 
			
		||||
                protocolFeeBalance: singleProtocolFee,
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('can continue to the bridge order if the native order reverts', async () => {
 | 
			
		||||
            const nativeOrders = [createOrder()];
 | 
			
		||||
            const bridgeOrders = [createBridgeOrder()];
 | 
			
		||||
            const orders = [...nativeOrders, ...bridgeOrders];
 | 
			
		||||
            const signatures = [
 | 
			
		||||
                ...nativeOrders.map(() => encodeExchangeBehavior()), // Valid Signatures
 | 
			
		||||
                ...bridgeOrders.map(() => NULL_BYTES), // Valid Signatures
 | 
			
		||||
            ];
 | 
			
		||||
            const qfr = getExpectedSellQuoteFillResults(bridgeOrders);
 | 
			
		||||
            await host
 | 
			
		||||
                .executeTransform(
 | 
			
		||||
                    transformer.address,
 | 
			
		||||
                    takerToken.address,
 | 
			
		||||
                    qfr.takerAssetSpent,
 | 
			
		||||
                    encodeTransformData({
 | 
			
		||||
                        orders,
 | 
			
		||||
                        signatures,
 | 
			
		||||
                    }),
 | 
			
		||||
                )
 | 
			
		||||
                // Insufficient single protocol fee
 | 
			
		||||
                .awaitTransactionSuccessAsync({ value: singleProtocolFee.minus(1) });
 | 
			
		||||
            assertBalances(await getBalancesAsync(host.address), {
 | 
			
		||||
                ...ZERO_BALANCES,
 | 
			
		||||
                makerAssetBalance: qfr.makerAssetBought,
 | 
			
		||||
                protocolFeeBalance: singleProtocolFee,
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -8,14 +8,17 @@ export * from '../test/generated-wrappers/allowance_target';
 | 
			
		||||
export * from '../test/generated-wrappers/bootstrap';
 | 
			
		||||
export * from '../test/generated-wrappers/fill_quote_transformer';
 | 
			
		||||
export * from '../test/generated-wrappers/fixin_common';
 | 
			
		||||
export * from '../test/generated-wrappers/fixin_gas_token';
 | 
			
		||||
export * from '../test/generated-wrappers/flash_wallet';
 | 
			
		||||
export * from '../test/generated-wrappers/full_migration';
 | 
			
		||||
export * from '../test/generated-wrappers/i_allowance_target';
 | 
			
		||||
export * from '../test/generated-wrappers/i_bootstrap';
 | 
			
		||||
export * from '../test/generated-wrappers/i_erc20_bridge';
 | 
			
		||||
export * from '../test/generated-wrappers/i_erc20_transformer';
 | 
			
		||||
export * from '../test/generated-wrappers/i_exchange';
 | 
			
		||||
export * from '../test/generated-wrappers/i_feature';
 | 
			
		||||
export * from '../test/generated-wrappers/i_flash_wallet';
 | 
			
		||||
export * from '../test/generated-wrappers/i_gas_token';
 | 
			
		||||
export * from '../test/generated-wrappers/i_ownable';
 | 
			
		||||
export * from '../test/generated-wrappers/i_simple_function_registry';
 | 
			
		||||
export * from '../test/generated-wrappers/i_test_simple_function_registry_feature';
 | 
			
		||||
@@ -43,6 +46,7 @@ export * from '../test/generated-wrappers/pay_taker_transformer';
 | 
			
		||||
export * from '../test/generated-wrappers/simple_function_registry';
 | 
			
		||||
export * from '../test/generated-wrappers/test_call_target';
 | 
			
		||||
export * from '../test/generated-wrappers/test_delegate_caller';
 | 
			
		||||
export * from '../test/generated-wrappers/test_fill_quote_transformer_bridge';
 | 
			
		||||
export * from '../test/generated-wrappers/test_fill_quote_transformer_exchange';
 | 
			
		||||
export * from '../test/generated-wrappers/test_fill_quote_transformer_host';
 | 
			
		||||
export * from '../test/generated-wrappers/test_full_migration';
 | 
			
		||||
 
 | 
			
		||||
@@ -26,14 +26,17 @@
 | 
			
		||||
        "test/generated-artifacts/Bootstrap.json",
 | 
			
		||||
        "test/generated-artifacts/FillQuoteTransformer.json",
 | 
			
		||||
        "test/generated-artifacts/FixinCommon.json",
 | 
			
		||||
        "test/generated-artifacts/FixinGasToken.json",
 | 
			
		||||
        "test/generated-artifacts/FlashWallet.json",
 | 
			
		||||
        "test/generated-artifacts/FullMigration.json",
 | 
			
		||||
        "test/generated-artifacts/IAllowanceTarget.json",
 | 
			
		||||
        "test/generated-artifacts/IBootstrap.json",
 | 
			
		||||
        "test/generated-artifacts/IERC20Bridge.json",
 | 
			
		||||
        "test/generated-artifacts/IERC20Transformer.json",
 | 
			
		||||
        "test/generated-artifacts/IExchange.json",
 | 
			
		||||
        "test/generated-artifacts/IFeature.json",
 | 
			
		||||
        "test/generated-artifacts/IFlashWallet.json",
 | 
			
		||||
        "test/generated-artifacts/IGasToken.json",
 | 
			
		||||
        "test/generated-artifacts/IOwnable.json",
 | 
			
		||||
        "test/generated-artifacts/ISimpleFunctionRegistry.json",
 | 
			
		||||
        "test/generated-artifacts/ITestSimpleFunctionRegistryFeature.json",
 | 
			
		||||
@@ -61,6 +64,7 @@
 | 
			
		||||
        "test/generated-artifacts/SimpleFunctionRegistry.json",
 | 
			
		||||
        "test/generated-artifacts/TestCallTarget.json",
 | 
			
		||||
        "test/generated-artifacts/TestDelegateCaller.json",
 | 
			
		||||
        "test/generated-artifacts/TestFillQuoteTransformerBridge.json",
 | 
			
		||||
        "test/generated-artifacts/TestFillQuoteTransformerExchange.json",
 | 
			
		||||
        "test/generated-artifacts/TestFillQuoteTransformerHost.json",
 | 
			
		||||
        "test/generated-artifacts/TestFullMigration.json",
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user