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:
parent
41cdbc0ec4
commit
406d2cefc5
@ -31,6 +31,10 @@
|
|||||||
{
|
{
|
||||||
"note": "Introduce fill `TransformERC20` feature.",
|
"note": "Introduce fill `TransformERC20` feature.",
|
||||||
"pr": 2545
|
"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 "@0x/contracts-utils/contracts/src/v06/LibMathV06.sol";
|
||||||
import "../errors/LibTransformERC20RichErrors.sol";
|
import "../errors/LibTransformERC20RichErrors.sol";
|
||||||
import "../vendor/v3/IExchange.sol";
|
import "../vendor/v3/IExchange.sol";
|
||||||
|
import "../vendor/v3/IERC20Bridge.sol";
|
||||||
import "./Transformer.sol";
|
import "./Transformer.sol";
|
||||||
import "./LibERC20Transformer.sol";
|
import "./LibERC20Transformer.sol";
|
||||||
|
import "../fixins/FixinGasToken.sol";
|
||||||
|
|
||||||
|
|
||||||
/// @dev A transformer that fills an ERC20 market sell/buy quote.
|
/// @dev A transformer that fills an ERC20 market sell/buy quote.
|
||||||
|
/// This transformer shortcuts bridge orders and fills them directly
|
||||||
contract FillQuoteTransformer is
|
contract FillQuoteTransformer is
|
||||||
Transformer
|
Transformer,
|
||||||
|
FixinGasToken
|
||||||
{
|
{
|
||||||
using LibERC20TokenV06 for IERC20TokenV06;
|
using LibERC20TokenV06 for IERC20TokenV06;
|
||||||
using LibERC20Transformer for IERC20TokenV06;
|
using LibERC20Transformer for IERC20TokenV06;
|
||||||
using LibSafeMathV06 for uint256;
|
using LibSafeMathV06 for uint256;
|
||||||
using LibRichErrorsV06 for bytes;
|
using LibRichErrorsV06 for bytes;
|
||||||
|
using LibBytesV06 for bytes;
|
||||||
|
|
||||||
/// @dev Whether we are performing a market sell or buy.
|
/// @dev Whether we are performing a market sell or buy.
|
||||||
enum Side {
|
enum Side {
|
||||||
@ -81,8 +86,29 @@ contract FillQuoteTransformer is
|
|||||||
uint256 protocolFeePaid;
|
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.
|
/// @dev The Exchange ERC20Proxy ID.
|
||||||
bytes4 private constant ERC20_ASSET_PROXY_ID = 0xf47261b0;
|
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.
|
/// @dev Maximum uint256 value.
|
||||||
uint256 private constant MAX_UINT256 = uint256(-1);
|
uint256 private constant MAX_UINT256 = uint256(-1);
|
||||||
|
|
||||||
@ -113,9 +139,11 @@ contract FillQuoteTransformer is
|
|||||||
)
|
)
|
||||||
external
|
external
|
||||||
override
|
override
|
||||||
|
freesGasTokensFromCollector
|
||||||
returns (bytes4 success)
|
returns (bytes4 success)
|
||||||
{
|
{
|
||||||
TransformData memory data = abi.decode(data_, (TransformData));
|
TransformData memory data = abi.decode(data_, (TransformData));
|
||||||
|
FillState memory state;
|
||||||
|
|
||||||
// Validate data fields.
|
// Validate data fields.
|
||||||
if (data.sellToken.isTokenETH() || data.buyToken.isTokenETH()) {
|
if (data.sellToken.isTokenETH() || data.buyToken.isTokenETH()) {
|
||||||
@ -131,43 +159,35 @@ contract FillQuoteTransformer is
|
|||||||
).rrevert();
|
).rrevert();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
state.takerTokenBalanceRemaining = data.sellToken.getTokenBalanceOf(address(this));
|
||||||
if (data.side == Side.Sell && data.fillAmount == MAX_UINT256) {
|
if (data.side == Side.Sell && data.fillAmount == MAX_UINT256) {
|
||||||
// If `sellAmount == -1 then we are selling
|
// If `sellAmount == -1 then we are selling
|
||||||
// the entire balance of `sellToken`. This is useful in cases where
|
// the entire balance of `sellToken`. This is useful in cases where
|
||||||
// the exact sell amount is not exactly known in advance, like when
|
// the exact sell amount is not exactly known in advance, like when
|
||||||
// unwrapping Chai/cUSDC/cDAI.
|
// unwrapping Chai/cUSDC/cDAI.
|
||||||
data.fillAmount = data.sellToken.getTokenBalanceOf(address(this));
|
data.fillAmount = state.takerTokenBalanceRemaining;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Approve the ERC20 proxy to spend `sellToken`.
|
// Approve the ERC20 proxy to spend `sellToken`.
|
||||||
data.sellToken.approveIfBelow(erc20Proxy, data.fillAmount);
|
data.sellToken.approveIfBelow(erc20Proxy, data.fillAmount);
|
||||||
|
|
||||||
// Fill the orders.
|
// Fill the orders.
|
||||||
uint256 singleProtocolFee = exchange.protocolFeeMultiplier().safeMul(tx.gasprice);
|
state.protocolFee = exchange.protocolFeeMultiplier().safeMul(tx.gasprice);
|
||||||
uint256 ethRemaining = address(this).balance;
|
state.ethRemaining = address(this).balance;
|
||||||
uint256 boughtAmount = 0;
|
|
||||||
uint256 soldAmount = 0;
|
|
||||||
for (uint256 i = 0; i < data.orders.length; ++i) {
|
for (uint256 i = 0; i < data.orders.length; ++i) {
|
||||||
// Check if we've hit our targets.
|
// Check if we've hit our targets.
|
||||||
if (data.side == Side.Sell) {
|
if (data.side == Side.Sell) {
|
||||||
// Market sell check.
|
// Market sell check.
|
||||||
if (soldAmount >= data.fillAmount) {
|
if (state.soldAmount >= data.fillAmount) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Market buy check.
|
// Market buy check.
|
||||||
if (boughtAmount >= data.fillAmount) {
|
if (state.boughtAmount >= data.fillAmount) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure we have enough ETH to cover the protocol fee.
|
|
||||||
if (ethRemaining < singleProtocolFee) {
|
|
||||||
LibTransformERC20RichErrors
|
|
||||||
.InsufficientProtocolFeeError(ethRemaining, singleProtocolFee)
|
|
||||||
.rrevert();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fill the order.
|
// Fill the order.
|
||||||
FillOrderResults memory results;
|
FillOrderResults memory results;
|
||||||
if (data.side == Side.Sell) {
|
if (data.side == Side.Sell) {
|
||||||
@ -177,12 +197,12 @@ contract FillQuoteTransformer is
|
|||||||
data.sellToken,
|
data.sellToken,
|
||||||
data.orders[i],
|
data.orders[i],
|
||||||
data.signatures[i],
|
data.signatures[i],
|
||||||
data.fillAmount.safeSub(soldAmount).min256(
|
data.fillAmount.safeSub(state.soldAmount).min256(
|
||||||
data.maxOrderFillAmounts.length > i
|
data.maxOrderFillAmounts.length > i
|
||||||
? data.maxOrderFillAmounts[i]
|
? data.maxOrderFillAmounts[i]
|
||||||
: MAX_UINT256
|
: MAX_UINT256
|
||||||
),
|
),
|
||||||
singleProtocolFee
|
state
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// Market buy.
|
// Market buy.
|
||||||
@ -191,39 +211,40 @@ contract FillQuoteTransformer is
|
|||||||
data.sellToken,
|
data.sellToken,
|
||||||
data.orders[i],
|
data.orders[i],
|
||||||
data.signatures[i],
|
data.signatures[i],
|
||||||
data.fillAmount.safeSub(boughtAmount).min256(
|
data.fillAmount.safeSub(state.boughtAmount).min256(
|
||||||
data.maxOrderFillAmounts.length > i
|
data.maxOrderFillAmounts.length > i
|
||||||
? data.maxOrderFillAmounts[i]
|
? data.maxOrderFillAmounts[i]
|
||||||
: MAX_UINT256
|
: MAX_UINT256
|
||||||
),
|
),
|
||||||
singleProtocolFee
|
state
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Accumulate totals.
|
// Accumulate totals.
|
||||||
soldAmount = soldAmount.safeAdd(results.takerTokenSoldAmount);
|
state.soldAmount = state.soldAmount.safeAdd(results.takerTokenSoldAmount);
|
||||||
boughtAmount = boughtAmount.safeAdd(results.makerTokenBoughtAmount);
|
state.boughtAmount = state.boughtAmount.safeAdd(results.makerTokenBoughtAmount);
|
||||||
ethRemaining = ethRemaining.safeSub(results.protocolFeePaid);
|
state.ethRemaining = state.ethRemaining.safeSub(results.protocolFeePaid);
|
||||||
|
state.takerTokenBalanceRemaining = state.takerTokenBalanceRemaining.safeSub(results.takerTokenSoldAmount);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure we hit our targets.
|
// Ensure we hit our targets.
|
||||||
if (data.side == Side.Sell) {
|
if (data.side == Side.Sell) {
|
||||||
// Market sell check.
|
// Market sell check.
|
||||||
if (soldAmount < data.fillAmount) {
|
if (state.soldAmount < data.fillAmount) {
|
||||||
LibTransformERC20RichErrors
|
LibTransformERC20RichErrors
|
||||||
.IncompleteFillSellQuoteError(
|
.IncompleteFillSellQuoteError(
|
||||||
address(data.sellToken),
|
address(data.sellToken),
|
||||||
soldAmount,
|
state.soldAmount,
|
||||||
data.fillAmount
|
data.fillAmount
|
||||||
).rrevert();
|
).rrevert();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Market buy check.
|
// Market buy check.
|
||||||
if (boughtAmount < data.fillAmount) {
|
if (state.boughtAmount < data.fillAmount) {
|
||||||
LibTransformERC20RichErrors
|
LibTransformERC20RichErrors
|
||||||
.IncompleteFillBuyQuoteError(
|
.IncompleteFillBuyQuoteError(
|
||||||
address(data.buyToken),
|
address(data.buyToken),
|
||||||
boughtAmount,
|
state.boughtAmount,
|
||||||
data.fillAmount
|
data.fillAmount
|
||||||
).rrevert();
|
).rrevert();
|
||||||
}
|
}
|
||||||
@ -237,14 +258,14 @@ contract FillQuoteTransformer is
|
|||||||
/// @param order The order to fill.
|
/// @param order The order to fill.
|
||||||
/// @param signature The signature for `order`.
|
/// @param signature The signature for `order`.
|
||||||
/// @param sellAmount Amount of taker token to sell.
|
/// @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(
|
function _sellToOrder(
|
||||||
IERC20TokenV06 makerToken,
|
IERC20TokenV06 makerToken,
|
||||||
IERC20TokenV06 takerToken,
|
IERC20TokenV06 takerToken,
|
||||||
IExchange.Order memory order,
|
IExchange.Order memory order,
|
||||||
bytes memory signature,
|
bytes memory signature,
|
||||||
uint256 sellAmount,
|
uint256 sellAmount,
|
||||||
uint256 protocolFee
|
FillState memory state
|
||||||
)
|
)
|
||||||
private
|
private
|
||||||
returns (FillOrderResults memory results)
|
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.
|
// Perform the fill.
|
||||||
return _fillOrder(
|
return _fillOrder(
|
||||||
order,
|
order,
|
||||||
signature,
|
signature,
|
||||||
takerTokenFillAmount,
|
takerTokenFillAmount,
|
||||||
protocolFee,
|
state,
|
||||||
makerToken,
|
makerToken,
|
||||||
takerFeeToken == takerToken
|
takerFeeToken == takerToken
|
||||||
);
|
);
|
||||||
@ -304,14 +319,14 @@ contract FillQuoteTransformer is
|
|||||||
/// @param order The order to fill.
|
/// @param order The order to fill.
|
||||||
/// @param signature The signature for `order`.
|
/// @param signature The signature for `order`.
|
||||||
/// @param buyAmount Amount of maker token to buy.
|
/// @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(
|
function _buyFromOrder(
|
||||||
IERC20TokenV06 makerToken,
|
IERC20TokenV06 makerToken,
|
||||||
IERC20TokenV06 takerToken,
|
IERC20TokenV06 takerToken,
|
||||||
IExchange.Order memory order,
|
IExchange.Order memory order,
|
||||||
bytes memory signature,
|
bytes memory signature,
|
||||||
uint256 buyAmount,
|
uint256 buyAmount,
|
||||||
uint256 protocolFee
|
FillState memory state
|
||||||
)
|
)
|
||||||
private
|
private
|
||||||
returns (FillOrderResults memory results)
|
returns (FillOrderResults memory results)
|
||||||
@ -351,18 +366,12 @@ contract FillQuoteTransformer is
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clamp to order size.
|
|
||||||
takerTokenFillAmount = LibSafeMathV06.min256(
|
|
||||||
order.takerAssetAmount,
|
|
||||||
takerTokenFillAmount
|
|
||||||
);
|
|
||||||
|
|
||||||
// Perform the fill.
|
// Perform the fill.
|
||||||
return _fillOrder(
|
return _fillOrder(
|
||||||
order,
|
order,
|
||||||
signature,
|
signature,
|
||||||
takerTokenFillAmount,
|
takerTokenFillAmount,
|
||||||
protocolFee,
|
state,
|
||||||
makerToken,
|
makerToken,
|
||||||
takerFeeToken == takerToken
|
takerFeeToken == takerToken
|
||||||
);
|
);
|
||||||
@ -373,7 +382,7 @@ contract FillQuoteTransformer is
|
|||||||
/// @param order The order to fill.
|
/// @param order The order to fill.
|
||||||
/// @param signature The order signature.
|
/// @param signature The order signature.
|
||||||
/// @param takerAssetFillAmount How much taker asset to fill.
|
/// @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 makerToken The maker token.
|
||||||
/// @param isTakerFeeInTakerToken Whether the taker fee token is the same as the
|
/// @param isTakerFeeInTakerToken Whether the taker fee token is the same as the
|
||||||
/// taker token.
|
/// taker token.
|
||||||
@ -381,38 +390,116 @@ contract FillQuoteTransformer is
|
|||||||
IExchange.Order memory order,
|
IExchange.Order memory order,
|
||||||
bytes memory signature,
|
bytes memory signature,
|
||||||
uint256 takerAssetFillAmount,
|
uint256 takerAssetFillAmount,
|
||||||
uint256 protocolFee,
|
FillState memory state,
|
||||||
IERC20TokenV06 makerToken,
|
IERC20TokenV06 makerToken,
|
||||||
bool isTakerFeeInTakerToken
|
bool isTakerFeeInTakerToken
|
||||||
)
|
)
|
||||||
private
|
private
|
||||||
returns (FillOrderResults memory results)
|
returns (FillOrderResults memory results)
|
||||||
{
|
{
|
||||||
// Track changes in the maker token balance.
|
// Clamp to remaining taker asset amount or order size.
|
||||||
uint256 initialMakerTokenBalance = makerToken.balanceOf(address(this));
|
uint256 availableTakerAssetFillAmount =
|
||||||
try
|
takerAssetFillAmount.min256(order.takerAssetAmount);
|
||||||
exchange.fillOrder
|
availableTakerAssetFillAmount =
|
||||||
{value: protocolFee}
|
availableTakerAssetFillAmount.min256(state.takerTokenBalanceRemaining);
|
||||||
(order, takerAssetFillAmount, signature)
|
// If it is a Bridge order we fill this directly
|
||||||
returns (IExchange.FillResults memory fillResults)
|
// rather than filling via 0x Exchange
|
||||||
{
|
if (order.makerAssetData.readBytes4(0) == ERC20_BRIDGE_PROXY_ID) {
|
||||||
// Update maker quantity based on changes in token balances.
|
// Calculate the amount (in maker token) we expect to receive
|
||||||
results.makerTokenBoughtAmount = makerToken.balanceOf(address(this))
|
// from the bridge
|
||||||
.safeSub(initialMakerTokenBalance);
|
uint256 outputTokenAmount = LibMathV06.getPartialAmountFloor(
|
||||||
// We can trust the other fill result quantities.
|
availableTakerAssetFillAmount,
|
||||||
results.protocolFeePaid = fillResults.protocolFeePaid;
|
order.takerAssetAmount,
|
||||||
results.takerTokenSoldAmount = fillResults.takerAssetFilledAmount;
|
order.makerAssetAmount
|
||||||
// If the taker fee is payable in the taker asset, include the
|
);
|
||||||
// taker fee in the total amount sold.
|
(bool success, bytes memory data) = address(_implementation).delegatecall(
|
||||||
if (isTakerFeeInTakerToken) {
|
abi.encodeWithSelector(
|
||||||
results.takerTokenSoldAmount =
|
this.fillBridgeOrder.selector,
|
||||||
results.takerTokenSoldAmount.safeAdd(fillResults.takerFeePaid);
|
order.makerAddress,
|
||||||
}
|
order.makerAssetData,
|
||||||
} catch (bytes memory) {
|
order.takerAssetData,
|
||||||
|
availableTakerAssetFillAmount,
|
||||||
|
outputTokenAmount
|
||||||
|
)
|
||||||
|
);
|
||||||
// Swallow failures, leaving all results as zero.
|
// 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: state.protocolFee}
|
||||||
|
(order, availableTakerAssetFillAmount, signature)
|
||||||
|
returns (IExchange.FillResults memory fillResults)
|
||||||
|
{
|
||||||
|
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) {
|
||||||
|
results.takerTokenSoldAmount =
|
||||||
|
results.takerTokenSoldAmount.safeAdd(fillResults.takerFeePaid);
|
||||||
|
}
|
||||||
|
} catch (bytes memory) {
|
||||||
|
// 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.
|
/// @dev Extract the token from plain ERC20 asset data.
|
||||||
/// If the asset-data is empty, a zero token address will be returned.
|
/// If the asset-data is empty, a zero token address will be returned.
|
||||||
/// @param assetData The order asset data.
|
/// @param assetData The order asset data.
|
||||||
|
@ -33,7 +33,7 @@ abstract contract Transformer is
|
|||||||
/// @dev The address of the deployer.
|
/// @dev The address of the deployer.
|
||||||
address public immutable deployer;
|
address public immutable deployer;
|
||||||
/// @dev The original address of this contract.
|
/// @dev The original address of this contract.
|
||||||
address private immutable _implementation;
|
address internal immutable _implementation;
|
||||||
|
|
||||||
/// @dev Create this contract.
|
/// @dev Create this contract.
|
||||||
constructor() public {
|
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",
|
"lint-contracts": "#solhint -c ../.solhint.json contracts/**/**/**/**/*.sol",
|
||||||
"compile:truffle": "truffle compile",
|
"compile:truffle": "truffle compile",
|
||||||
"docs:md": "ts-doc-gen --sourceDir='$PROJECT_FILES' --output=$MD_FILE_DIR --fileExtension=mdx --tsconfig=./typedoc-tsconfig.json",
|
"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": {
|
"config": {
|
||||||
"publicInterfaceContracts": "ZeroEx,FullMigration,InitialMigration,IFlashWallet,IAllowanceTarget,IERC20Transformer,IOwnable,ISimpleFunctionRegistry,ITokenSpender,ITransformERC20,FillQuoteTransformer,PayTakerTransformer,WethTransformer,Ownable,SimpleFunctionRegistry,TransformERC20,TokenSpender,AffiliateFeeTransformer",
|
"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: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": {
|
"repository": {
|
||||||
"type": "git",
|
"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 Bootstrap from '../test/generated-artifacts/Bootstrap.json';
|
||||||
import * as FillQuoteTransformer from '../test/generated-artifacts/FillQuoteTransformer.json';
|
import * as FillQuoteTransformer from '../test/generated-artifacts/FillQuoteTransformer.json';
|
||||||
import * as FixinCommon from '../test/generated-artifacts/FixinCommon.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 FlashWallet from '../test/generated-artifacts/FlashWallet.json';
|
||||||
import * as FullMigration from '../test/generated-artifacts/FullMigration.json';
|
import * as FullMigration from '../test/generated-artifacts/FullMigration.json';
|
||||||
import * as IAllowanceTarget from '../test/generated-artifacts/IAllowanceTarget.json';
|
import * as IAllowanceTarget from '../test/generated-artifacts/IAllowanceTarget.json';
|
||||||
import * as IBootstrap from '../test/generated-artifacts/IBootstrap.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 IERC20Transformer from '../test/generated-artifacts/IERC20Transformer.json';
|
||||||
import * as IExchange from '../test/generated-artifacts/IExchange.json';
|
import * as IExchange from '../test/generated-artifacts/IExchange.json';
|
||||||
import * as IFeature from '../test/generated-artifacts/IFeature.json';
|
import * as IFeature from '../test/generated-artifacts/IFeature.json';
|
||||||
import * as IFlashWallet from '../test/generated-artifacts/IFlashWallet.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 InitialMigration from '../test/generated-artifacts/InitialMigration.json';
|
||||||
import * as IOwnable from '../test/generated-artifacts/IOwnable.json';
|
import * as IOwnable from '../test/generated-artifacts/IOwnable.json';
|
||||||
import * as ISimpleFunctionRegistry from '../test/generated-artifacts/ISimpleFunctionRegistry.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 SimpleFunctionRegistry from '../test/generated-artifacts/SimpleFunctionRegistry.json';
|
||||||
import * as TestCallTarget from '../test/generated-artifacts/TestCallTarget.json';
|
import * as TestCallTarget from '../test/generated-artifacts/TestCallTarget.json';
|
||||||
import * as TestDelegateCaller from '../test/generated-artifacts/TestDelegateCaller.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 TestFillQuoteTransformerExchange from '../test/generated-artifacts/TestFillQuoteTransformerExchange.json';
|
||||||
import * as TestFillQuoteTransformerHost from '../test/generated-artifacts/TestFillQuoteTransformerHost.json';
|
import * as TestFillQuoteTransformerHost from '../test/generated-artifacts/TestFillQuoteTransformerHost.json';
|
||||||
import * as TestFullMigration from '../test/generated-artifacts/TestFullMigration.json';
|
import * as TestFullMigration from '../test/generated-artifacts/TestFullMigration.json';
|
||||||
@ -95,6 +99,7 @@ export const artifacts = {
|
|||||||
TokenSpender: TokenSpender as ContractArtifact,
|
TokenSpender: TokenSpender as ContractArtifact,
|
||||||
TransformERC20: TransformERC20 as ContractArtifact,
|
TransformERC20: TransformERC20 as ContractArtifact,
|
||||||
FixinCommon: FixinCommon as ContractArtifact,
|
FixinCommon: FixinCommon as ContractArtifact,
|
||||||
|
FixinGasToken: FixinGasToken as ContractArtifact,
|
||||||
FullMigration: FullMigration as ContractArtifact,
|
FullMigration: FullMigration as ContractArtifact,
|
||||||
InitialMigration: InitialMigration as ContractArtifact,
|
InitialMigration: InitialMigration as ContractArtifact,
|
||||||
LibBootstrap: LibBootstrap as ContractArtifact,
|
LibBootstrap: LibBootstrap as ContractArtifact,
|
||||||
@ -112,10 +117,13 @@ export const artifacts = {
|
|||||||
PayTakerTransformer: PayTakerTransformer as ContractArtifact,
|
PayTakerTransformer: PayTakerTransformer as ContractArtifact,
|
||||||
Transformer: Transformer as ContractArtifact,
|
Transformer: Transformer as ContractArtifact,
|
||||||
WethTransformer: WethTransformer as ContractArtifact,
|
WethTransformer: WethTransformer as ContractArtifact,
|
||||||
|
IERC20Bridge: IERC20Bridge as ContractArtifact,
|
||||||
IExchange: IExchange as ContractArtifact,
|
IExchange: IExchange as ContractArtifact,
|
||||||
|
IGasToken: IGasToken as ContractArtifact,
|
||||||
ITestSimpleFunctionRegistryFeature: ITestSimpleFunctionRegistryFeature as ContractArtifact,
|
ITestSimpleFunctionRegistryFeature: ITestSimpleFunctionRegistryFeature as ContractArtifact,
|
||||||
TestCallTarget: TestCallTarget as ContractArtifact,
|
TestCallTarget: TestCallTarget as ContractArtifact,
|
||||||
TestDelegateCaller: TestDelegateCaller as ContractArtifact,
|
TestDelegateCaller: TestDelegateCaller as ContractArtifact,
|
||||||
|
TestFillQuoteTransformerBridge: TestFillQuoteTransformerBridge as ContractArtifact,
|
||||||
TestFillQuoteTransformerExchange: TestFillQuoteTransformerExchange as ContractArtifact,
|
TestFillQuoteTransformerExchange: TestFillQuoteTransformerExchange as ContractArtifact,
|
||||||
TestFillQuoteTransformerHost: TestFillQuoteTransformerHost as ContractArtifact,
|
TestFillQuoteTransformerHost: TestFillQuoteTransformerHost as ContractArtifact,
|
||||||
TestFullMigration: TestFullMigration as ContractArtifact,
|
TestFullMigration: TestFullMigration as ContractArtifact,
|
||||||
|
@ -18,6 +18,7 @@ import { BigNumber, hexUtils, ZeroExRevertErrors } from '@0x/utils';
|
|||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
import { artifacts } from '../artifacts';
|
import { artifacts } from '../artifacts';
|
||||||
|
import { TestFillQuoteTransformerBridgeContract } from '../generated-wrappers/test_fill_quote_transformer_bridge';
|
||||||
import {
|
import {
|
||||||
FillQuoteTransformerContract,
|
FillQuoteTransformerContract,
|
||||||
TestFillQuoteTransformerExchangeContract,
|
TestFillQuoteTransformerExchangeContract,
|
||||||
@ -31,6 +32,7 @@ blockchainTests.resets('FillQuoteTransformer', env => {
|
|||||||
let maker: string;
|
let maker: string;
|
||||||
let feeRecipient: string;
|
let feeRecipient: string;
|
||||||
let exchange: TestFillQuoteTransformerExchangeContract;
|
let exchange: TestFillQuoteTransformerExchangeContract;
|
||||||
|
let bridge: TestFillQuoteTransformerBridgeContract;
|
||||||
let transformer: FillQuoteTransformerContract;
|
let transformer: FillQuoteTransformerContract;
|
||||||
let host: TestFillQuoteTransformerHostContract;
|
let host: TestFillQuoteTransformerHostContract;
|
||||||
let makerToken: TestMintableERC20TokenContract;
|
let makerToken: TestMintableERC20TokenContract;
|
||||||
@ -64,6 +66,12 @@ blockchainTests.resets('FillQuoteTransformer', env => {
|
|||||||
},
|
},
|
||||||
artifacts,
|
artifacts,
|
||||||
);
|
);
|
||||||
|
bridge = await TestFillQuoteTransformerBridgeContract.deployFrom0xArtifactAsync(
|
||||||
|
artifacts.TestFillQuoteTransformerBridge,
|
||||||
|
env.provider,
|
||||||
|
env.txDefaults,
|
||||||
|
artifacts,
|
||||||
|
);
|
||||||
[makerToken, takerToken, takerFeeToken] = await Promise.all(
|
[makerToken, takerToken, takerFeeToken] = await Promise.all(
|
||||||
_.times(3, async () =>
|
_.times(3, async () =>
|
||||||
TestMintableERC20TokenContract.deployFrom0xArtifactAsync(
|
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 {
|
interface QuoteFillResults {
|
||||||
makerAssetBought: BigNumber;
|
makerAssetBought: BigNumber;
|
||||||
takerAssetSpent: 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';
|
const ERC20_ASSET_PROXY_ID = '0xf47261b0';
|
||||||
|
|
||||||
describe('sell quotes', () => {
|
describe('sell quotes', () => {
|
||||||
@ -436,9 +468,10 @@ blockchainTests.resets('FillQuoteTransformer', env => {
|
|||||||
)
|
)
|
||||||
.awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid.minus(1) });
|
.awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid.minus(1) });
|
||||||
return expect(tx).to.revertWith(
|
return expect(tx).to.revertWith(
|
||||||
new ZeroExRevertErrors.TransformERC20.InsufficientProtocolFeeError(
|
new ZeroExRevertErrors.TransformERC20.IncompleteFillSellQuoteError(
|
||||||
singleProtocolFee.minus(1),
|
takerToken.address,
|
||||||
singleProtocolFee,
|
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 () => {
|
it('fails to buy more than available in orders', async () => {
|
||||||
const orders = _.times(3, () => createOrder());
|
const orders = _.times(3, () => createOrder());
|
||||||
const signatures = orders.map(() => encodeExchangeBehavior());
|
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/bootstrap';
|
||||||
export * from '../test/generated-wrappers/fill_quote_transformer';
|
export * from '../test/generated-wrappers/fill_quote_transformer';
|
||||||
export * from '../test/generated-wrappers/fixin_common';
|
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/flash_wallet';
|
||||||
export * from '../test/generated-wrappers/full_migration';
|
export * from '../test/generated-wrappers/full_migration';
|
||||||
export * from '../test/generated-wrappers/i_allowance_target';
|
export * from '../test/generated-wrappers/i_allowance_target';
|
||||||
export * from '../test/generated-wrappers/i_bootstrap';
|
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_erc20_transformer';
|
||||||
export * from '../test/generated-wrappers/i_exchange';
|
export * from '../test/generated-wrappers/i_exchange';
|
||||||
export * from '../test/generated-wrappers/i_feature';
|
export * from '../test/generated-wrappers/i_feature';
|
||||||
export * from '../test/generated-wrappers/i_flash_wallet';
|
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_ownable';
|
||||||
export * from '../test/generated-wrappers/i_simple_function_registry';
|
export * from '../test/generated-wrappers/i_simple_function_registry';
|
||||||
export * from '../test/generated-wrappers/i_test_simple_function_registry_feature';
|
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/simple_function_registry';
|
||||||
export * from '../test/generated-wrappers/test_call_target';
|
export * from '../test/generated-wrappers/test_call_target';
|
||||||
export * from '../test/generated-wrappers/test_delegate_caller';
|
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_exchange';
|
||||||
export * from '../test/generated-wrappers/test_fill_quote_transformer_host';
|
export * from '../test/generated-wrappers/test_fill_quote_transformer_host';
|
||||||
export * from '../test/generated-wrappers/test_full_migration';
|
export * from '../test/generated-wrappers/test_full_migration';
|
||||||
|
@ -26,14 +26,17 @@
|
|||||||
"test/generated-artifacts/Bootstrap.json",
|
"test/generated-artifacts/Bootstrap.json",
|
||||||
"test/generated-artifacts/FillQuoteTransformer.json",
|
"test/generated-artifacts/FillQuoteTransformer.json",
|
||||||
"test/generated-artifacts/FixinCommon.json",
|
"test/generated-artifacts/FixinCommon.json",
|
||||||
|
"test/generated-artifacts/FixinGasToken.json",
|
||||||
"test/generated-artifacts/FlashWallet.json",
|
"test/generated-artifacts/FlashWallet.json",
|
||||||
"test/generated-artifacts/FullMigration.json",
|
"test/generated-artifacts/FullMigration.json",
|
||||||
"test/generated-artifacts/IAllowanceTarget.json",
|
"test/generated-artifacts/IAllowanceTarget.json",
|
||||||
"test/generated-artifacts/IBootstrap.json",
|
"test/generated-artifacts/IBootstrap.json",
|
||||||
|
"test/generated-artifacts/IERC20Bridge.json",
|
||||||
"test/generated-artifacts/IERC20Transformer.json",
|
"test/generated-artifacts/IERC20Transformer.json",
|
||||||
"test/generated-artifacts/IExchange.json",
|
"test/generated-artifacts/IExchange.json",
|
||||||
"test/generated-artifacts/IFeature.json",
|
"test/generated-artifacts/IFeature.json",
|
||||||
"test/generated-artifacts/IFlashWallet.json",
|
"test/generated-artifacts/IFlashWallet.json",
|
||||||
|
"test/generated-artifacts/IGasToken.json",
|
||||||
"test/generated-artifacts/IOwnable.json",
|
"test/generated-artifacts/IOwnable.json",
|
||||||
"test/generated-artifacts/ISimpleFunctionRegistry.json",
|
"test/generated-artifacts/ISimpleFunctionRegistry.json",
|
||||||
"test/generated-artifacts/ITestSimpleFunctionRegistryFeature.json",
|
"test/generated-artifacts/ITestSimpleFunctionRegistryFeature.json",
|
||||||
@ -61,6 +64,7 @@
|
|||||||
"test/generated-artifacts/SimpleFunctionRegistry.json",
|
"test/generated-artifacts/SimpleFunctionRegistry.json",
|
||||||
"test/generated-artifacts/TestCallTarget.json",
|
"test/generated-artifacts/TestCallTarget.json",
|
||||||
"test/generated-artifacts/TestDelegateCaller.json",
|
"test/generated-artifacts/TestDelegateCaller.json",
|
||||||
|
"test/generated-artifacts/TestFillQuoteTransformerBridge.json",
|
||||||
"test/generated-artifacts/TestFillQuoteTransformerExchange.json",
|
"test/generated-artifacts/TestFillQuoteTransformerExchange.json",
|
||||||
"test/generated-artifacts/TestFillQuoteTransformerHost.json",
|
"test/generated-artifacts/TestFillQuoteTransformerHost.json",
|
||||||
"test/generated-artifacts/TestFullMigration.json",
|
"test/generated-artifacts/TestFullMigration.json",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user