@0x/contracts-zero-ex: Introduce transformer contracts.
This commit is contained in:
committed by
Lawrence Forman
parent
0e1a5a375a
commit
2ba3818b65
@@ -98,6 +98,21 @@ library LibTransformERC20RichErrors {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Common Transformer errors ///////////////////////////////////////////////
|
||||||
|
|
||||||
|
function InvalidTransformDataError(
|
||||||
|
bytes memory transformData
|
||||||
|
)
|
||||||
|
internal
|
||||||
|
pure
|
||||||
|
returns (bytes memory)
|
||||||
|
{
|
||||||
|
return abi.encodeWithSelector(
|
||||||
|
bytes4(keccak256("InvalidTransformDataError(bytes)")),
|
||||||
|
transformData
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// FillQuoteTransformer errors /////////////////////////////////////////////
|
// FillQuoteTransformer errors /////////////////////////////////////////////
|
||||||
|
|
||||||
function IncompleteFillSellQuoteError(
|
function IncompleteFillSellQuoteError(
|
||||||
@@ -177,24 +192,7 @@ library LibTransformERC20RichErrors {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// WethTransformer errors ////////////////////////////////////////////////////
|
function InvalidTakerFeeTokenError(
|
||||||
|
|
||||||
function WrongNumberOfTokensReceivedError(
|
|
||||||
uint256 actual,
|
|
||||||
uint256 expected
|
|
||||||
)
|
|
||||||
internal
|
|
||||||
pure
|
|
||||||
returns (bytes memory)
|
|
||||||
{
|
|
||||||
return abi.encodeWithSelector(
|
|
||||||
bytes4(keccak256("WrongNumberOfTokensReceivedError(uint256,uint256)")),
|
|
||||||
actual,
|
|
||||||
expected
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function InvalidTokenReceivedError(
|
|
||||||
address token
|
address token
|
||||||
)
|
)
|
||||||
internal
|
internal
|
||||||
@@ -202,8 +200,9 @@ library LibTransformERC20RichErrors {
|
|||||||
returns (bytes memory)
|
returns (bytes memory)
|
||||||
{
|
{
|
||||||
return abi.encodeWithSelector(
|
return abi.encodeWithSelector(
|
||||||
bytes4(keccak256("InvalidTokenReceivedError(address)")),
|
bytes4(keccak256("InvalidTakerFeeTokenError(address)")),
|
||||||
token
|
token
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,418 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
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/errors/LibRichErrorsV06.sol";
|
||||||
|
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
|
||||||
|
import "@0x/contracts-utils/contracts/src/v06/LibBytesV06.sol";
|
||||||
|
import "@0x/contracts-erc20/contracts/src/v06/LibERC20TokenV06.sol";
|
||||||
|
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 "./IERC20Transformer.sol";
|
||||||
|
import "./LibERC20Transformer.sol";
|
||||||
|
|
||||||
|
|
||||||
|
/// @dev A transformer that fills an ERC20 market sell/buy quote.
|
||||||
|
contract FillQuoteTransformer is
|
||||||
|
IERC20Transformer
|
||||||
|
{
|
||||||
|
// solhint-disable indent,no-empty-blocks,no-unused-vars
|
||||||
|
|
||||||
|
/// @dev Transform data to ABI-encode and pass into `transform()`.
|
||||||
|
struct TransformData {
|
||||||
|
// The token being sold.
|
||||||
|
// This should be an actual token, not the ETH pseudo-token.
|
||||||
|
IERC20TokenV06 sellToken;
|
||||||
|
// The token being bought.
|
||||||
|
// This should be an actual token, not the ETH pseudo-token.
|
||||||
|
IERC20TokenV06 buyToken;
|
||||||
|
// The orders to fill.
|
||||||
|
IExchange.Order[] orders;
|
||||||
|
// Signatures for each respective order in `orders`.
|
||||||
|
bytes[] signatures;
|
||||||
|
// Maximum fill amount for each order. This may be shorter than the
|
||||||
|
// number of orders, where missing entries will be treated as `uint256(-1)`.
|
||||||
|
// For sells, this will be the maximum sell amount (taker asset).
|
||||||
|
// For buys, this will be the maximum buy amount (maker asset).
|
||||||
|
uint256[] maxOrderFillAmounts;
|
||||||
|
// Amount of `sellToken` to sell. May be `uint256(-1)` to sell entire
|
||||||
|
// amount of `sellToken` received. Zero if performing a market buy.
|
||||||
|
uint256 sellAmount;
|
||||||
|
// Amount of `buyToken` to buy. Zero if performing a market sell.
|
||||||
|
uint256 buyAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Results of a call to `_fillOrder()`.
|
||||||
|
struct FillOrderResults {
|
||||||
|
// The amount of taker tokens sold, according to balance checks.
|
||||||
|
uint256 takerTokenSoldAmount;
|
||||||
|
// The amount of maker tokens sold, according to balance checks.
|
||||||
|
uint256 makerTokenBoughtAmount;
|
||||||
|
// The amount of protocol fee paid.
|
||||||
|
uint256 protocolFeePaid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev The Exchange ERC20Proxy ID.
|
||||||
|
bytes4 constant private ERC20_ASSET_PROXY_ID = 0xf47261b0;
|
||||||
|
|
||||||
|
/// @dev The Exchange contract.
|
||||||
|
IExchange public immutable exchange;
|
||||||
|
/// @dev The ERC20Proxy address.
|
||||||
|
address public immutable erc20Proxy;
|
||||||
|
|
||||||
|
using LibERC20TokenV06 for IERC20TokenV06;
|
||||||
|
using LibERC20Transformer for IERC20TokenV06;
|
||||||
|
using LibSafeMathV06 for uint256;
|
||||||
|
using LibRichErrorsV06 for bytes;
|
||||||
|
|
||||||
|
constructor(IExchange exchange_) public {
|
||||||
|
exchange = exchange_;
|
||||||
|
erc20Proxy = exchange_.getAssetProxy(ERC20_ASSET_PROXY_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Sell this contract's entire balance of of `sellToken` in exchange
|
||||||
|
/// for `buyToken` by filling `orders`. Protocol fees should be attached
|
||||||
|
/// to this call. `buyToken` and excess ETH will be transferred back to the caller.
|
||||||
|
/// This function cannot be re-entered.
|
||||||
|
/// @param data_ ABI-encoded `TransformData`.
|
||||||
|
/// @return success `TRANSFORMER_SUCCESS` on success.
|
||||||
|
function transform(
|
||||||
|
bytes32, // callDataHash,
|
||||||
|
address payable, // taker,
|
||||||
|
bytes calldata data_
|
||||||
|
)
|
||||||
|
external
|
||||||
|
override
|
||||||
|
returns (bytes4 success)
|
||||||
|
{
|
||||||
|
TransformData memory data = abi.decode(data_, (TransformData));
|
||||||
|
|
||||||
|
// Validate data fields.
|
||||||
|
if (data.sellToken.isTokenETH() ||
|
||||||
|
data.buyToken.isTokenETH() ||
|
||||||
|
data.orders.length != data.signatures.length)
|
||||||
|
{
|
||||||
|
LibTransformERC20RichErrors.InvalidTransformDataError(data_).rrevert();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If `sellAmount == -1` and `buyAmount == 0` 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.
|
||||||
|
if (data.sellAmount == uint256(-1) && data.buyAmount == 0) {
|
||||||
|
data.sellAmount = data.sellToken.getTokenBalanceOf(address(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Approve the ERC20 proxy to spend `sellToken`.
|
||||||
|
data.sellToken.approveIfBelow(erc20Proxy, data.sellAmount);
|
||||||
|
|
||||||
|
// Fill the orders.
|
||||||
|
uint256 singleProtocolFee = exchange.protocolFeeMultiplier().safeMul(tx.gasprice);
|
||||||
|
uint256 ethRemaining = address(this).balance;
|
||||||
|
uint256 boughtAmount = 0;
|
||||||
|
uint256 soldAmount = 0;
|
||||||
|
for (uint256 i = 0; i < data.orders.length; ++i) {
|
||||||
|
// Check if we've hit our targets.
|
||||||
|
if (data.buyAmount == 0) {
|
||||||
|
// Market sell check.
|
||||||
|
if (soldAmount >= data.sellAmount) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Market buy check.
|
||||||
|
if (boughtAmount >= data.buyAmount) {
|
||||||
|
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.buyAmount == 0) {
|
||||||
|
// Market sell.
|
||||||
|
results = _sellToOrder(
|
||||||
|
data.buyToken,
|
||||||
|
data.sellToken,
|
||||||
|
data.orders[i],
|
||||||
|
data.signatures[i],
|
||||||
|
data.sellAmount.safeSub(soldAmount).min256(
|
||||||
|
data.maxOrderFillAmounts.length > i
|
||||||
|
? data.maxOrderFillAmounts[i]
|
||||||
|
: uint256(-1)
|
||||||
|
),
|
||||||
|
singleProtocolFee
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Market buy.
|
||||||
|
results = _buyFromOrder(
|
||||||
|
data.buyToken,
|
||||||
|
data.sellToken,
|
||||||
|
data.orders[i],
|
||||||
|
data.signatures[i],
|
||||||
|
data.buyAmount.safeSub(boughtAmount).min256(
|
||||||
|
data.maxOrderFillAmounts.length > i
|
||||||
|
? data.maxOrderFillAmounts[i]
|
||||||
|
: uint256(-1)
|
||||||
|
),
|
||||||
|
singleProtocolFee
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accumulate totals.
|
||||||
|
soldAmount = soldAmount.safeAdd(results.takerTokenSoldAmount);
|
||||||
|
boughtAmount = boughtAmount.safeAdd(results.makerTokenBoughtAmount);
|
||||||
|
ethRemaining = ethRemaining.safeSub(results.protocolFeePaid);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure we hit our targets.
|
||||||
|
if (data.buyAmount == 0) {
|
||||||
|
// Market sell check.
|
||||||
|
if (soldAmount < data.sellAmount) {
|
||||||
|
LibTransformERC20RichErrors
|
||||||
|
.IncompleteFillSellQuoteError(
|
||||||
|
address(data.sellToken),
|
||||||
|
soldAmount,
|
||||||
|
data.sellAmount
|
||||||
|
).rrevert();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Market buy check.
|
||||||
|
if (boughtAmount < data.buyAmount) {
|
||||||
|
LibTransformERC20RichErrors
|
||||||
|
.IncompleteFillBuyQuoteError(
|
||||||
|
address(data.buyToken),
|
||||||
|
boughtAmount,
|
||||||
|
data.buyAmount
|
||||||
|
).rrevert();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return LibERC20Transformer.TRANSFORMER_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Try to sell up to `sellAmount` from an order.
|
||||||
|
/// @param makerToken The maker/buy token.
|
||||||
|
/// @param takerToken The taker/sell token.
|
||||||
|
/// @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`.
|
||||||
|
function _sellToOrder(
|
||||||
|
IERC20TokenV06 makerToken,
|
||||||
|
IERC20TokenV06 takerToken,
|
||||||
|
IExchange.Order memory order,
|
||||||
|
bytes memory signature,
|
||||||
|
uint256 sellAmount,
|
||||||
|
uint256 protocolFee
|
||||||
|
)
|
||||||
|
private
|
||||||
|
returns (FillOrderResults memory results)
|
||||||
|
{
|
||||||
|
IERC20TokenV06 takerFeeToken = order.takerFeeAssetData.length == 0
|
||||||
|
? IERC20TokenV06(address(0))
|
||||||
|
: _getTokenFromERC20AssetData(order.takerFeeAssetData);
|
||||||
|
|
||||||
|
uint256 takerTokenFillAmount = sellAmount;
|
||||||
|
|
||||||
|
if (order.takerFee != 0) {
|
||||||
|
if (takerFeeToken == makerToken) {
|
||||||
|
// Taker fee is payable in the maker token, so we need to
|
||||||
|
// approve the proxy to spend the maker token.
|
||||||
|
// It isn't worth computing the actual taker fee
|
||||||
|
// since `approveIfBelow()` will set the allowance to infinite. We
|
||||||
|
// just need a reasonable upper bound to avoid unnecessarily re-approving.
|
||||||
|
takerFeeToken.approveIfBelow(erc20Proxy, order.takerFee);
|
||||||
|
} else if (takerFeeToken == takerToken){
|
||||||
|
// Taker fee is payable in the taker token, so we need to
|
||||||
|
// reduce the fill amount to cover the fee.
|
||||||
|
// takerTokenFillAmount' =
|
||||||
|
// (takerTokenFillAmount * order.takerAssetAmount) /
|
||||||
|
// (order.takerAssetAmount + order.takerFee)
|
||||||
|
takerTokenFillAmount = LibMathV06.getPartialAmountCeil(
|
||||||
|
order.takerAssetAmount,
|
||||||
|
order.takerAssetAmount.safeAdd(order.takerFee),
|
||||||
|
takerTokenFillAmount
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Only support taker or maker asset denominated taker fees.
|
||||||
|
LibTransformERC20RichErrors.InvalidTakerFeeTokenError(
|
||||||
|
address(takerFeeToken)
|
||||||
|
).rrevert();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clamp fill amount to order size.
|
||||||
|
takerTokenFillAmount = LibSafeMathV06.min256(
|
||||||
|
takerTokenFillAmount,
|
||||||
|
order.takerAssetAmount
|
||||||
|
);
|
||||||
|
|
||||||
|
// Perform the fill.
|
||||||
|
return _fillOrder(
|
||||||
|
order,
|
||||||
|
signature,
|
||||||
|
takerTokenFillAmount,
|
||||||
|
protocolFee,
|
||||||
|
makerToken,
|
||||||
|
takerFeeToken == takerToken
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Try to buy up to `buyAmount` from an order.
|
||||||
|
/// @param makerToken The maker/buy token.
|
||||||
|
/// @param takerToken The taker/sell token.
|
||||||
|
/// @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`.
|
||||||
|
function _buyFromOrder(
|
||||||
|
IERC20TokenV06 makerToken,
|
||||||
|
IERC20TokenV06 takerToken,
|
||||||
|
IExchange.Order memory order,
|
||||||
|
bytes memory signature,
|
||||||
|
uint256 buyAmount,
|
||||||
|
uint256 protocolFee
|
||||||
|
)
|
||||||
|
private
|
||||||
|
returns (FillOrderResults memory results)
|
||||||
|
{
|
||||||
|
IERC20TokenV06 takerFeeToken = order.takerFeeAssetData.length == 0
|
||||||
|
? IERC20TokenV06(address(0))
|
||||||
|
: _getTokenFromERC20AssetData(order.takerFeeAssetData);
|
||||||
|
|
||||||
|
uint256 makerTokenFillAmount = buyAmount;
|
||||||
|
|
||||||
|
if (order.takerFee != 0) {
|
||||||
|
if (takerFeeToken == makerToken) {
|
||||||
|
// Taker fee is payable in the maker token.
|
||||||
|
// Increase the fill amount to account for maker tokens being
|
||||||
|
// lost to the taker fee.
|
||||||
|
// makerTokenFillAmount' =
|
||||||
|
// (order.makerAssetAmount * makerTokenFillAmount) /
|
||||||
|
// (order.makerAssetAmount - order.takerFee)
|
||||||
|
makerTokenFillAmount = LibMathV06.getPartialAmountCeil(
|
||||||
|
order.makerAssetAmount,
|
||||||
|
order.makerAssetAmount.safeSub(order.takerFee),
|
||||||
|
makerTokenFillAmount
|
||||||
|
);
|
||||||
|
// Approve the proxy to spend the maker token.
|
||||||
|
// It isn't worth computing the actual taker fee
|
||||||
|
// since `approveIfBelow()` will set the allowance to infinite. We
|
||||||
|
// just need a reasonable upper bound to avoid unnecessarily re-approving.
|
||||||
|
takerFeeToken.approveIfBelow(erc20Proxy, order.takerFee);
|
||||||
|
} else if (takerFeeToken != takerToken) {
|
||||||
|
// Only support taker or maker asset denominated taker fees.
|
||||||
|
LibTransformERC20RichErrors.InvalidTakerFeeTokenError(
|
||||||
|
address(takerFeeToken)
|
||||||
|
).rrevert();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert maker fill amount to taker fill amount.
|
||||||
|
uint256 takerTokenFillAmount = LibSafeMathV06.min256(
|
||||||
|
order.takerAssetAmount,
|
||||||
|
LibMathV06.getPartialAmountCeil(
|
||||||
|
makerTokenFillAmount,
|
||||||
|
order.makerAssetAmount,
|
||||||
|
order.takerAssetAmount
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Perform the fill.
|
||||||
|
return _fillOrder(
|
||||||
|
order,
|
||||||
|
signature,
|
||||||
|
takerTokenFillAmount,
|
||||||
|
protocolFee,
|
||||||
|
makerToken,
|
||||||
|
takerFeeToken == takerToken
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Attempt to fill an order. If the fill reverts, the revert will be
|
||||||
|
/// swallowed and `results` will be zeroed out.
|
||||||
|
/// @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 makerToken The maker token.
|
||||||
|
/// @param isTakerFeeInTakerToken Whether the taker fee token is the same as the
|
||||||
|
/// taker token.
|
||||||
|
function _fillOrder(
|
||||||
|
IExchange.Order memory order,
|
||||||
|
bytes memory signature,
|
||||||
|
uint256 takerAssetFillAmount,
|
||||||
|
uint256 protocolFee,
|
||||||
|
IERC20TokenV06 makerToken,
|
||||||
|
bool isTakerFeeInTakerToken
|
||||||
|
)
|
||||||
|
private
|
||||||
|
returns (FillOrderResults memory results)
|
||||||
|
{
|
||||||
|
// Track changes in the maker token balance.
|
||||||
|
results.makerTokenBoughtAmount = makerToken.balanceOf(address(this));
|
||||||
|
try
|
||||||
|
exchange.fillOrder
|
||||||
|
{value: protocolFee}
|
||||||
|
(order, takerAssetFillAmount, signature)
|
||||||
|
returns (IExchange.FillResults memory fillResults)
|
||||||
|
{
|
||||||
|
// Update maker quantity based on changes in token balances.
|
||||||
|
results.makerTokenBoughtAmount = makerToken.balanceOf(address(this))
|
||||||
|
.safeSub(results.makerTokenBoughtAmount);
|
||||||
|
// We can trust the other fill result quantities.
|
||||||
|
results.protocolFeePaid = fillResults.protocolFeePaid;
|
||||||
|
results.takerTokenSoldAmount = fillResults.takerAssetFilledAmount;
|
||||||
|
// 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) {
|
||||||
|
// If the fill fails, zero out fill quantities.
|
||||||
|
results.makerTokenBoughtAmount = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Extract the token from plain ERC20 asset data.
|
||||||
|
/// @param assetData The order asset data.
|
||||||
|
function _getTokenFromERC20AssetData(bytes memory assetData)
|
||||||
|
private
|
||||||
|
pure
|
||||||
|
returns (IERC20TokenV06 token)
|
||||||
|
{
|
||||||
|
if (assetData.length != 36 ||
|
||||||
|
LibBytesV06.readBytes4(assetData, 0) != ERC20_ASSET_PROXY_ID)
|
||||||
|
{
|
||||||
|
LibTransformERC20RichErrors
|
||||||
|
.InvalidERC20AssetDataError(assetData)
|
||||||
|
.rrevert();
|
||||||
|
}
|
||||||
|
return IERC20TokenV06(LibBytesV06.readAddress(assetData, 16));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
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/errors/LibRichErrorsV06.sol";
|
||||||
|
import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
|
||||||
|
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
|
||||||
|
import "@0x/contracts-erc20/contracts/src/v06/LibERC20TokenV06.sol";
|
||||||
|
import "../errors/LibTransformERC20RichErrors.sol";
|
||||||
|
import "./IERC20Transformer.sol";
|
||||||
|
import "./LibERC20Transformer.sol";
|
||||||
|
|
||||||
|
|
||||||
|
/// @dev A transformer that transfers tokens to the taker.
|
||||||
|
contract PayTakerTransformer is
|
||||||
|
IERC20Transformer
|
||||||
|
{
|
||||||
|
/// @dev Transform data to ABI-encode and pass into `transform()`.
|
||||||
|
struct TransformData {
|
||||||
|
// The tokens to transfer to the taker.
|
||||||
|
IERC20TokenV06[] tokens;
|
||||||
|
// Amount of each token in `tokens` to transfer to the taker.
|
||||||
|
// `uint(-1)` will transfer the entire balance.
|
||||||
|
uint256[] amounts;
|
||||||
|
}
|
||||||
|
|
||||||
|
using LibRichErrorsV06 for bytes;
|
||||||
|
using LibSafeMathV06 for uint256;
|
||||||
|
using LibERC20Transformer for IERC20TokenV06;
|
||||||
|
|
||||||
|
/// @dev Forwards tokens to the taker.
|
||||||
|
/// @param taker The taker address (caller of `TransformERC20.transformERC20()`).
|
||||||
|
/// @param data_ ABI-encoded `TransformData`, indicating which tokens to transfer.
|
||||||
|
/// @return success `TRANSFORMER_SUCCESS` on success.
|
||||||
|
function transform(
|
||||||
|
bytes32, // callDataHash,
|
||||||
|
address payable taker,
|
||||||
|
bytes calldata data_
|
||||||
|
)
|
||||||
|
external
|
||||||
|
override
|
||||||
|
returns (bytes4 success)
|
||||||
|
{
|
||||||
|
TransformData memory data = abi.decode(data_, (TransformData));
|
||||||
|
|
||||||
|
// Transfer tokens directly to the taker.
|
||||||
|
for (uint256 i = 0; i < data.tokens.length; ++i) {
|
||||||
|
// The `amounts` array can be shorter than the `tokens` array.
|
||||||
|
// Missing elements are treated as `uint256(-1)`.
|
||||||
|
uint256 amount = data.amounts.length > i ? data.amounts[i] : uint256(-1);
|
||||||
|
if (amount == uint256(-1)) {
|
||||||
|
amount = data.tokens[i].getTokenBalanceOf(address(this));
|
||||||
|
}
|
||||||
|
if (amount != 0) {
|
||||||
|
data.tokens[i].transformerTransfer(taker, amount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return LibERC20Transformer.TRANSFORMER_SUCCESS;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
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/errors/LibRichErrorsV06.sol";
|
||||||
|
import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
|
||||||
|
import "@0x/contracts-erc20/contracts/src/v06/IEtherTokenV06.sol";
|
||||||
|
import "../errors/LibTransformERC20RichErrors.sol";
|
||||||
|
import "./IERC20Transformer.sol";
|
||||||
|
import "./LibERC20Transformer.sol";
|
||||||
|
|
||||||
|
|
||||||
|
/// @dev A transformer that wraps or unwraps WETH.
|
||||||
|
contract WethTransformer is
|
||||||
|
IERC20Transformer
|
||||||
|
{
|
||||||
|
/// @dev Transform data to ABI-encode and pass into `transform()`.
|
||||||
|
struct TransformData {
|
||||||
|
// The token to wrap/unwrap. Must be either ETH or WETH.
|
||||||
|
IERC20TokenV06 token;
|
||||||
|
// Amount of `token` to wrap or unwrap.
|
||||||
|
// `uint(-1)` will unwrap the entire balance.
|
||||||
|
uint256 amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
// solhint-disable
|
||||||
|
/// @dev The WETH contract address.
|
||||||
|
IEtherTokenV06 public immutable weth;
|
||||||
|
// solhint-enable
|
||||||
|
|
||||||
|
using LibRichErrorsV06 for bytes;
|
||||||
|
using LibSafeMathV06 for uint256;
|
||||||
|
using LibERC20Transformer for IERC20TokenV06;
|
||||||
|
|
||||||
|
/// @dev Construct the transformer and store the WETH address in an immutable.
|
||||||
|
/// @param weth_ The weth token.
|
||||||
|
constructor(IEtherTokenV06 weth_) public {
|
||||||
|
weth = weth_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Wraps and unwraps WETH.
|
||||||
|
/// @param data_ ABI-encoded `TransformData`, indicating which token to wrap/umwrap.
|
||||||
|
/// @return success `TRANSFORMER_SUCCESS` on success.
|
||||||
|
function transform(
|
||||||
|
bytes32, // callDataHash,
|
||||||
|
address payable, // taker,
|
||||||
|
bytes calldata data_
|
||||||
|
)
|
||||||
|
external
|
||||||
|
override
|
||||||
|
returns (bytes4 success)
|
||||||
|
{
|
||||||
|
TransformData memory data = abi.decode(data_, (TransformData));
|
||||||
|
if (!data.token.isTokenETH() && data.token != weth) {
|
||||||
|
LibTransformERC20RichErrors.InvalidTransformDataError(data_).rrevert();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint256 amount = data.amount;
|
||||||
|
if (amount == uint256(-1)) {
|
||||||
|
amount = data.token.getTokenBalanceOf(address(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (amount != 0) {
|
||||||
|
if (data.token.isTokenETH()) {
|
||||||
|
// Wrap ETH.
|
||||||
|
weth.deposit{value: amount}();
|
||||||
|
} else {
|
||||||
|
// Unwrap WETH.
|
||||||
|
weth.withdraw(amount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return LibERC20Transformer.TRANSFORMER_SUCCESS;
|
||||||
|
}
|
||||||
|
}
|
||||||
107
contracts/zero-ex/contracts/src/vendor/v3/IExchange.sol
vendored
Normal file
107
contracts/zero-ex/contracts/src/vendor/v3/IExchange.sol
vendored
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
|
||||||
|
/// @dev Interface to the V3 Exchange.
|
||||||
|
interface IExchange {
|
||||||
|
|
||||||
|
/// @dev V3 Order structure.
|
||||||
|
struct Order {
|
||||||
|
// Address that created the order.
|
||||||
|
address makerAddress;
|
||||||
|
// Address that is allowed to fill the order.
|
||||||
|
// If set to 0, any address is allowed to fill the order.
|
||||||
|
address takerAddress;
|
||||||
|
// Address that will recieve fees when order is filled.
|
||||||
|
address feeRecipientAddress;
|
||||||
|
// Address that is allowed to call Exchange contract methods that affect this order.
|
||||||
|
// If set to 0, any address is allowed to call these methods.
|
||||||
|
address senderAddress;
|
||||||
|
// Amount of makerAsset being offered by maker. Must be greater than 0.
|
||||||
|
uint256 makerAssetAmount;
|
||||||
|
// Amount of takerAsset being bid on by maker. Must be greater than 0.
|
||||||
|
uint256 takerAssetAmount;
|
||||||
|
// Fee paid to feeRecipient by maker when order is filled.
|
||||||
|
uint256 makerFee;
|
||||||
|
// Fee paid to feeRecipient by taker when order is filled.
|
||||||
|
uint256 takerFee;
|
||||||
|
// Timestamp in seconds at which order expires.
|
||||||
|
uint256 expirationTimeSeconds;
|
||||||
|
// Arbitrary number to facilitate uniqueness of the order's hash.
|
||||||
|
uint256 salt;
|
||||||
|
// Encoded data that can be decoded by a specified proxy contract when transferring makerAsset.
|
||||||
|
// The leading bytes4 references the id of the asset proxy.
|
||||||
|
bytes makerAssetData;
|
||||||
|
// Encoded data that can be decoded by a specified proxy contract when transferring takerAsset.
|
||||||
|
// The leading bytes4 references the id of the asset proxy.
|
||||||
|
bytes takerAssetData;
|
||||||
|
// Encoded data that can be decoded by a specified proxy contract when transferring makerFeeAsset.
|
||||||
|
// The leading bytes4 references the id of the asset proxy.
|
||||||
|
bytes makerFeeAssetData;
|
||||||
|
// Encoded data that can be decoded by a specified proxy contract when transferring takerFeeAsset.
|
||||||
|
// The leading bytes4 references the id of the asset proxy.
|
||||||
|
bytes takerFeeAssetData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev V3 `fillOrder()` results.`
|
||||||
|
struct FillResults {
|
||||||
|
// Total amount of makerAsset(s) filled.
|
||||||
|
uint256 makerAssetFilledAmount;
|
||||||
|
// Total amount of takerAsset(s) filled.
|
||||||
|
uint256 takerAssetFilledAmount;
|
||||||
|
// Total amount of fees paid by maker(s) to feeRecipient(s).
|
||||||
|
uint256 makerFeePaid;
|
||||||
|
// Total amount of fees paid by taker to feeRecipients(s).
|
||||||
|
uint256 takerFeePaid;
|
||||||
|
// Total amount of fees paid by taker to the staking contract.
|
||||||
|
uint256 protocolFeePaid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Fills the input order.
|
||||||
|
/// @param order Order struct containing order specifications.
|
||||||
|
/// @param takerAssetFillAmount Desired amount of takerAsset to sell.
|
||||||
|
/// @param signature Proof that order has been created by maker.
|
||||||
|
/// @return fillResults Amounts filled and fees paid by maker and taker.
|
||||||
|
function fillOrder(
|
||||||
|
Order calldata order,
|
||||||
|
uint256 takerAssetFillAmount,
|
||||||
|
bytes calldata signature
|
||||||
|
)
|
||||||
|
external
|
||||||
|
payable
|
||||||
|
returns (FillResults memory fillResults);
|
||||||
|
|
||||||
|
/// @dev Returns the protocolFeeMultiplier
|
||||||
|
/// @return multiplier The multiplier for protocol fees.
|
||||||
|
function protocolFeeMultiplier()
|
||||||
|
external
|
||||||
|
view
|
||||||
|
returns (uint256 multiplier);
|
||||||
|
|
||||||
|
/// @dev Gets an asset proxy.
|
||||||
|
/// @param assetProxyId Id of the asset proxy.
|
||||||
|
/// @return proxyAddress The asset proxy registered to assetProxyId.
|
||||||
|
/// Returns 0x0 if no proxy is registered.
|
||||||
|
function getAssetProxy(bytes4 assetProxyId)
|
||||||
|
external
|
||||||
|
view
|
||||||
|
returns (address proxyAddress);
|
||||||
|
}
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
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/LibBytesV06.sol";
|
||||||
|
import "@0x/contracts-utils/contracts/src/v06/LibMathV06.sol";
|
||||||
|
import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol";
|
||||||
|
import "../src/vendor/v3/IExchange.sol";
|
||||||
|
import "./TestMintableERC20Token.sol";
|
||||||
|
|
||||||
|
|
||||||
|
contract TestFillQuoteTransformerExchange {
|
||||||
|
|
||||||
|
struct FillBehavior {
|
||||||
|
// How much of the order is filled, in taker asset amount.
|
||||||
|
uint256 filledTakerAssetAmount;
|
||||||
|
// Scaling for maker assets minted, in 1e18.
|
||||||
|
uint256 makerAssetMintRatio;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint256 private constant PROTOCOL_FEE_MULTIPLIER = 1337;
|
||||||
|
|
||||||
|
using LibSafeMathV06 for uint256;
|
||||||
|
|
||||||
|
function fillOrder(
|
||||||
|
IExchange.Order calldata order,
|
||||||
|
uint256 takerAssetFillAmount,
|
||||||
|
bytes calldata signature
|
||||||
|
)
|
||||||
|
external
|
||||||
|
payable
|
||||||
|
returns (IExchange.FillResults memory fillResults)
|
||||||
|
{
|
||||||
|
require(
|
||||||
|
signature.length != 0,
|
||||||
|
"TestFillQuoteTransformerExchange/INVALID_SIGNATURE"
|
||||||
|
);
|
||||||
|
// The signature is the ABI-encoded FillBehavior data.
|
||||||
|
FillBehavior memory behavior = abi.decode(signature, (FillBehavior));
|
||||||
|
|
||||||
|
uint256 protocolFee = PROTOCOL_FEE_MULTIPLIER * tx.gasprice;
|
||||||
|
require(
|
||||||
|
msg.value == protocolFee,
|
||||||
|
"TestFillQuoteTransformerExchange/INSUFFICIENT_PROTOCOL_FEE"
|
||||||
|
);
|
||||||
|
// Return excess protocol fee.
|
||||||
|
msg.sender.transfer(msg.value - protocolFee);
|
||||||
|
|
||||||
|
// Take taker tokens.
|
||||||
|
TestMintableERC20Token takerToken = _getTokenFromAssetData(order.takerAssetData);
|
||||||
|
takerAssetFillAmount = LibSafeMathV06.min256(
|
||||||
|
order.takerAssetAmount.safeSub(behavior.filledTakerAssetAmount),
|
||||||
|
takerAssetFillAmount
|
||||||
|
);
|
||||||
|
require(
|
||||||
|
takerToken.getSpendableAmount(msg.sender, address(this)) >= takerAssetFillAmount,
|
||||||
|
"TestFillQuoteTransformerExchange/INSUFFICIENT_TAKER_FUNDS"
|
||||||
|
);
|
||||||
|
takerToken.transferFrom(msg.sender, order.makerAddress, takerAssetFillAmount);
|
||||||
|
|
||||||
|
// Mint maker tokens.
|
||||||
|
uint256 makerAssetFilledAmount = LibMathV06.getPartialAmountFloor(
|
||||||
|
takerAssetFillAmount,
|
||||||
|
order.takerAssetAmount,
|
||||||
|
order.makerAssetAmount
|
||||||
|
);
|
||||||
|
TestMintableERC20Token makerToken = _getTokenFromAssetData(order.makerAssetData);
|
||||||
|
makerToken.mint(
|
||||||
|
msg.sender,
|
||||||
|
LibMathV06.getPartialAmountFloor(
|
||||||
|
behavior.makerAssetMintRatio,
|
||||||
|
1e18,
|
||||||
|
makerAssetFilledAmount
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Take taker fee.
|
||||||
|
TestMintableERC20Token takerFeeToken = _getTokenFromAssetData(order.takerFeeAssetData);
|
||||||
|
uint256 takerFee = LibMathV06.getPartialAmountFloor(
|
||||||
|
takerAssetFillAmount,
|
||||||
|
order.takerAssetAmount,
|
||||||
|
order.takerFee
|
||||||
|
);
|
||||||
|
require(
|
||||||
|
takerFeeToken.getSpendableAmount(msg.sender, address(this)) >= takerFee,
|
||||||
|
"TestFillQuoteTransformerExchange/INSUFFICIENT_TAKER_FEE_FUNDS"
|
||||||
|
);
|
||||||
|
takerFeeToken.transferFrom(msg.sender, order.feeRecipientAddress, takerFee);
|
||||||
|
|
||||||
|
fillResults.makerAssetFilledAmount = makerAssetFilledAmount;
|
||||||
|
fillResults.takerAssetFilledAmount = takerAssetFillAmount;
|
||||||
|
fillResults.makerFeePaid = uint256(-1);
|
||||||
|
fillResults.takerFeePaid = takerFee;
|
||||||
|
fillResults.protocolFeePaid = protocolFee;
|
||||||
|
}
|
||||||
|
|
||||||
|
function encodeBehaviorData(FillBehavior calldata behavior)
|
||||||
|
external
|
||||||
|
pure
|
||||||
|
returns (bytes memory encoded)
|
||||||
|
{
|
||||||
|
return abi.encode(behavior);
|
||||||
|
}
|
||||||
|
|
||||||
|
function protocolFeeMultiplier()
|
||||||
|
external
|
||||||
|
pure
|
||||||
|
returns (uint256)
|
||||||
|
{
|
||||||
|
return PROTOCOL_FEE_MULTIPLIER;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAssetProxy(bytes4)
|
||||||
|
external
|
||||||
|
view
|
||||||
|
returns (address)
|
||||||
|
{
|
||||||
|
return address(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _getTokenFromAssetData(bytes memory assetData)
|
||||||
|
private
|
||||||
|
pure
|
||||||
|
returns (TestMintableERC20Token token)
|
||||||
|
{
|
||||||
|
return TestMintableERC20Token(LibBytesV06.readAddress(assetData, 16));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
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 "../src/transformers/IERC20Transformer.sol";
|
||||||
|
import "./TestMintableERC20Token.sol";
|
||||||
|
import "./TestTransformerHost.sol";
|
||||||
|
|
||||||
|
|
||||||
|
contract TestFillQuoteTransformerHost is
|
||||||
|
TestTransformerHost
|
||||||
|
{
|
||||||
|
function executeTransform(
|
||||||
|
IERC20Transformer transformer,
|
||||||
|
TestMintableERC20Token inputToken,
|
||||||
|
uint256 inputTokenAmount,
|
||||||
|
bytes calldata data
|
||||||
|
)
|
||||||
|
external
|
||||||
|
payable
|
||||||
|
{
|
||||||
|
if (inputTokenAmount != 0) {
|
||||||
|
inputToken.mint(address(this), inputTokenAmount);
|
||||||
|
}
|
||||||
|
// Have to make this call externally because transformers aren't payable.
|
||||||
|
this.rawExecuteTransform(transformer, bytes32(0), msg.sender, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -74,4 +74,14 @@ contract TestMintableERC20Token {
|
|||||||
balanceOf[to] += amount;
|
balanceOf[to] += amount;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getSpendableAmount(address owner, address spender)
|
||||||
|
external
|
||||||
|
view
|
||||||
|
returns (uint256)
|
||||||
|
{
|
||||||
|
return balanceOf[owner] < allowance[owner][spender]
|
||||||
|
? balanceOf[owner]
|
||||||
|
: allowance[owner][spender];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
60
contracts/zero-ex/contracts/test/TestTransformerHost.sol
Normal file
60
contracts/zero-ex/contracts/test/TestTransformerHost.sol
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
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/errors/LibRichErrorsV06.sol";
|
||||||
|
import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol";
|
||||||
|
import "../src/transformers/IERC20Transformer.sol";
|
||||||
|
import "../src/transformers/LibERC20Transformer.sol";
|
||||||
|
|
||||||
|
|
||||||
|
contract TestTransformerHost {
|
||||||
|
|
||||||
|
using LibERC20Transformer for IERC20TokenV06;
|
||||||
|
using LibRichErrorsV06 for bytes;
|
||||||
|
|
||||||
|
function rawExecuteTransform(
|
||||||
|
IERC20Transformer transformer,
|
||||||
|
bytes32 callDataHash,
|
||||||
|
address taker,
|
||||||
|
bytes calldata data
|
||||||
|
)
|
||||||
|
external
|
||||||
|
{
|
||||||
|
(bool success, bytes memory resultData) =
|
||||||
|
address(transformer).delegatecall(abi.encodeWithSelector(
|
||||||
|
transformer.transform.selector,
|
||||||
|
callDataHash,
|
||||||
|
taker,
|
||||||
|
data
|
||||||
|
));
|
||||||
|
if (!success) {
|
||||||
|
resultData.rrevert();
|
||||||
|
}
|
||||||
|
require(
|
||||||
|
abi.decode(resultData, (bytes4)) == LibERC20Transformer.TRANSFORMER_SUCCESS,
|
||||||
|
"TestFillQuoteTransformerTaker/UNSUCCESSFUL_RESULT"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// solhint-disable
|
||||||
|
receive() external payable {}
|
||||||
|
// solhint-enable
|
||||||
|
}
|
||||||
42
contracts/zero-ex/contracts/test/TestWeth.sol
Normal file
42
contracts/zero-ex/contracts/test/TestWeth.sol
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
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 "./TestMintableERC20Token.sol";
|
||||||
|
|
||||||
|
|
||||||
|
contract TestWeth is
|
||||||
|
TestMintableERC20Token
|
||||||
|
{
|
||||||
|
function deposit()
|
||||||
|
external
|
||||||
|
payable
|
||||||
|
{
|
||||||
|
this.mint(msg.sender, msg.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function withdraw(uint256 amount)
|
||||||
|
external
|
||||||
|
{
|
||||||
|
require(balanceOf[msg.sender] >= amount, "TestWeth/INSUFFICIENT_FUNDS");
|
||||||
|
balanceOf[msg.sender] -= amount;
|
||||||
|
msg.sender.transfer(amount);
|
||||||
|
}
|
||||||
|
}
|
||||||
53
contracts/zero-ex/contracts/test/TestWethTransformerHost.sol
Normal file
53
contracts/zero-ex/contracts/test/TestWethTransformerHost.sol
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
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 "../src/transformers/IERC20Transformer.sol";
|
||||||
|
import "./TestMintableERC20Token.sol";
|
||||||
|
import "./TestTransformerHost.sol";
|
||||||
|
import "./TestWeth.sol";
|
||||||
|
|
||||||
|
|
||||||
|
contract TestWethTransformerHost is
|
||||||
|
TestTransformerHost
|
||||||
|
{
|
||||||
|
// solhint-disable
|
||||||
|
TestWeth private immutable _weth;
|
||||||
|
// solhint-enable
|
||||||
|
|
||||||
|
constructor(TestWeth weth) public {
|
||||||
|
_weth = weth;
|
||||||
|
}
|
||||||
|
|
||||||
|
function executeTransform(
|
||||||
|
uint256 wethAmount,
|
||||||
|
IERC20Transformer transformer,
|
||||||
|
bytes calldata data
|
||||||
|
)
|
||||||
|
external
|
||||||
|
payable
|
||||||
|
{
|
||||||
|
if (wethAmount != 0) {
|
||||||
|
_weth.deposit{value: wethAmount}();
|
||||||
|
}
|
||||||
|
// Have to make this call externally because transformers aren't payable.
|
||||||
|
this.rawExecuteTransform(transformer, bytes32(0), msg.sender, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
"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"
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"publicInterfaceContracts": "ZeroEx,FullMigration,InitialMigration,IFlashWallet,IAllowanceTarget,IERC20Transformer,IOwnable,ISimpleFunctionRegistry,ITokenSpender,ITransformERC20",
|
"publicInterfaceContracts": "ZeroEx,FullMigration,InitialMigration,IFlashWallet,IAllowanceTarget,IERC20Transformer,IOwnable,ISimpleFunctionRegistry,ITokenSpender,ITransformERC20,FillQuoteTransformer,PayTakerTransformer,WethTransformer",
|
||||||
"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/@(AllowanceTarget|Bootstrap|FixinCommon|FlashWallet|FullMigration|IAllowanceTarget|IBootstrap|IERC20Transformer|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|SimpleFunctionRegistry|TestCallTarget|TestFullMigration|TestInitialMigration|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC20Token|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestTokenSpender|TestTokenSpenderERC20Token|TestTransformERC20|TestZeroExFeature|TokenSpender|TransformERC20|ZeroEx).json"
|
"abis": "./test/generated-artifacts/@(AllowanceTarget|Bootstrap|FixinCommon|FlashWallet|FullMigration|IAllowanceTarget|IBootstrap|IERC20Transformer|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|SimpleFunctionRegistry|TestCallTarget|TestFullMigration|TestInitialMigration|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC20Token|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestTokenSpender|TestTokenSpenderERC20Token|TestTransformERC20|TestZeroExFeature|TokenSpender|TransformERC20|ZeroEx).json"
|
||||||
},
|
},
|
||||||
@@ -56,6 +56,7 @@
|
|||||||
"@0x/contracts-gen": "^2.0.8",
|
"@0x/contracts-gen": "^2.0.8",
|
||||||
"@0x/contracts-test-utils": "^5.3.2",
|
"@0x/contracts-test-utils": "^5.3.2",
|
||||||
"@0x/dev-utils": "^3.2.1",
|
"@0x/dev-utils": "^3.2.1",
|
||||||
|
"@0x/order-utils": "^10.2.4",
|
||||||
"@0x/sol-compiler": "^4.0.8",
|
"@0x/sol-compiler": "^4.0.8",
|
||||||
"@0x/subproviders": "^6.0.8",
|
"@0x/subproviders": "^6.0.8",
|
||||||
"@0x/ts-doc-gen": "^0.0.22",
|
"@0x/ts-doc-gen": "^0.0.22",
|
||||||
|
|||||||
@@ -5,25 +5,31 @@
|
|||||||
*/
|
*/
|
||||||
import { ContractArtifact } from 'ethereum-types';
|
import { ContractArtifact } from 'ethereum-types';
|
||||||
|
|
||||||
|
import * as FillQuoteTransformer from '../generated-artifacts/FillQuoteTransformer.json';
|
||||||
import * as FullMigration from '../generated-artifacts/FullMigration.json';
|
import * as FullMigration from '../generated-artifacts/FullMigration.json';
|
||||||
import * as IAllowanceTarget from '../generated-artifacts/IAllowanceTarget.json';
|
|
||||||
import * as IERC20Transformer from '../generated-artifacts/IERC20Transformer.json';
|
import * as IERC20Transformer from '../generated-artifacts/IERC20Transformer.json';
|
||||||
import * as IFlashWallet from '../generated-artifacts/IFlashWallet.json';
|
|
||||||
import * as InitialMigration from '../generated-artifacts/InitialMigration.json';
|
import * as InitialMigration from '../generated-artifacts/InitialMigration.json';
|
||||||
import * as IOwnable from '../generated-artifacts/IOwnable.json';
|
import * as IOwnable from '../generated-artifacts/IOwnable.json';
|
||||||
import * as ISimpleFunctionRegistry from '../generated-artifacts/ISimpleFunctionRegistry.json';
|
import * as ISimpleFunctionRegistry from '../generated-artifacts/ISimpleFunctionRegistry.json';
|
||||||
import * as ITokenSpender from '../generated-artifacts/ITokenSpender.json';
|
import * as ITokenSpender from '../generated-artifacts/ITokenSpender.json';
|
||||||
import * as ITransformERC20 from '../generated-artifacts/ITransformERC20.json';
|
import * as ITransformERC20 from '../generated-artifacts/ITransformERC20.json';
|
||||||
|
import * as LibERC20Transformer from '../generated-artifacts/LibERC20Transformer.json';
|
||||||
|
import * as PayTakerTransformer from '../generated-artifacts/PayTakerTransformer.json';
|
||||||
|
import * as Puppet from '../generated-artifacts/Puppet.json';
|
||||||
|
import * as WethTransformer from '../generated-artifacts/WethTransformer.json';
|
||||||
import * as ZeroEx from '../generated-artifacts/ZeroEx.json';
|
import * as ZeroEx from '../generated-artifacts/ZeroEx.json';
|
||||||
export const artifacts = {
|
export const artifacts = {
|
||||||
ZeroEx: ZeroEx as ContractArtifact,
|
ZeroEx: ZeroEx as ContractArtifact,
|
||||||
FullMigration: FullMigration as ContractArtifact,
|
FullMigration: FullMigration as ContractArtifact,
|
||||||
InitialMigration: InitialMigration as ContractArtifact,
|
InitialMigration: InitialMigration as ContractArtifact,
|
||||||
IFlashWallet: IFlashWallet as ContractArtifact,
|
Puppet: Puppet as ContractArtifact,
|
||||||
IAllowanceTarget: IAllowanceTarget as ContractArtifact,
|
|
||||||
IERC20Transformer: IERC20Transformer as ContractArtifact,
|
IERC20Transformer: IERC20Transformer as ContractArtifact,
|
||||||
IOwnable: IOwnable as ContractArtifact,
|
IOwnable: IOwnable as ContractArtifact,
|
||||||
ISimpleFunctionRegistry: ISimpleFunctionRegistry as ContractArtifact,
|
ISimpleFunctionRegistry: ISimpleFunctionRegistry as ContractArtifact,
|
||||||
ITokenSpender: ITokenSpender as ContractArtifact,
|
ITokenSpender: ITokenSpender as ContractArtifact,
|
||||||
ITransformERC20: ITransformERC20 as ContractArtifact,
|
ITransformERC20: ITransformERC20 as ContractArtifact,
|
||||||
|
LibERC20Transformer: LibERC20Transformer as ContractArtifact,
|
||||||
|
PayTakerTransformer: PayTakerTransformer as ContractArtifact,
|
||||||
|
WethTransformer: WethTransformer as ContractArtifact,
|
||||||
|
FillQuoteTransformer: FillQuoteTransformer as ContractArtifact,
|
||||||
};
|
};
|
||||||
|
|||||||
4
contracts/zero-ex/src/constants.ts
Normal file
4
contracts/zero-ex/src/constants.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
/*
|
||||||
|
* The pseudo-token address for ETH used by `tranformERC20()`.
|
||||||
|
*/
|
||||||
|
export const ETH_TOKEN_ADDRESS = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee';
|
||||||
@@ -1,9 +1,14 @@
|
|||||||
export { artifacts } from './artifacts';
|
export { artifacts } from './artifacts';
|
||||||
export {
|
export {
|
||||||
|
FillQuoteTransformerContract,
|
||||||
IOwnableContract,
|
IOwnableContract,
|
||||||
IOwnableEvents,
|
IOwnableEvents,
|
||||||
ISimpleFunctionRegistryContract,
|
ISimpleFunctionRegistryContract,
|
||||||
ISimpleFunctionRegistryEvents,
|
ISimpleFunctionRegistryEvents,
|
||||||
|
ITokenSpenderContract,
|
||||||
|
ITransformERC20Contract,
|
||||||
|
PayTakerTransformerContract,
|
||||||
|
WethTransformerContract,
|
||||||
ZeroExContract,
|
ZeroExContract,
|
||||||
} from './wrappers';
|
} from './wrappers';
|
||||||
export { ZeroExRevertErrors } from '@0x/utils';
|
export { ZeroExRevertErrors } from '@0x/utils';
|
||||||
@@ -36,4 +41,6 @@ export {
|
|||||||
TupleDataItem,
|
TupleDataItem,
|
||||||
StateMutability,
|
StateMutability,
|
||||||
} from 'ethereum-types';
|
} from 'ethereum-types';
|
||||||
export { rlpEncodeNonce } from './nonce_utils';
|
|
||||||
|
export * from './constants';
|
||||||
|
export * from './transformer_data_encoders';
|
||||||
|
|||||||
114
contracts/zero-ex/src/transformer_data_encoders.ts
Normal file
114
contracts/zero-ex/src/transformer_data_encoders.ts
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
import { Order } from '@0x/types';
|
||||||
|
import { AbiEncoder, BigNumber } from '@0x/utils';
|
||||||
|
|
||||||
|
const ORDER_ABI_COMPONENTS = [
|
||||||
|
{ name: 'makerAddress', type: 'address' },
|
||||||
|
{ name: 'takerAddress', type: 'address' },
|
||||||
|
{ name: 'feeRecipientAddress', type: 'address' },
|
||||||
|
{ name: 'senderAddress', type: 'address' },
|
||||||
|
{ name: 'makerAssetAmount', type: 'uint256' },
|
||||||
|
{ name: 'takerAssetAmount', type: 'uint256' },
|
||||||
|
{ name: 'makerFee', type: 'uint256' },
|
||||||
|
{ name: 'takerFee', type: 'uint256' },
|
||||||
|
{ name: 'expirationTimeSeconds', type: 'uint256' },
|
||||||
|
{ name: 'salt', type: 'uint256' },
|
||||||
|
{ name: 'makerAssetData', type: 'bytes' },
|
||||||
|
{ name: 'takerAssetData', type: 'bytes' },
|
||||||
|
{ name: 'makerFeeAssetData', type: 'bytes' },
|
||||||
|
{ name: 'takerFeeAssetData', type: 'bytes' },
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ABI encoder for `FillQuoteTransformer.TransformData`
|
||||||
|
*/
|
||||||
|
export const fillQuoteTransformerDataEncoder = AbiEncoder.create([
|
||||||
|
{
|
||||||
|
name: 'data',
|
||||||
|
type: 'tuple',
|
||||||
|
components: [
|
||||||
|
{ name: 'sellToken', type: 'address' },
|
||||||
|
{ name: 'buyToken', type: 'address' },
|
||||||
|
{
|
||||||
|
name: 'orders',
|
||||||
|
type: 'tuple[]',
|
||||||
|
components: ORDER_ABI_COMPONENTS,
|
||||||
|
},
|
||||||
|
{ name: 'signatures', type: 'bytes[]' },
|
||||||
|
{ name: 'maxOrderFillAmounts', type: 'uint256[]' },
|
||||||
|
{ name: 'sellAmount', type: 'uint256' },
|
||||||
|
{ name: 'buyAmount', type: 'uint256' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `FillQuoteTransformer.TransformData`
|
||||||
|
*/
|
||||||
|
export interface FillQuoteTransformerData {
|
||||||
|
sellToken: string;
|
||||||
|
buyToken: string;
|
||||||
|
orders: Array<Exclude<Order, ['signature', 'exchangeAddress', 'chainId']>>;
|
||||||
|
signatures: string[];
|
||||||
|
maxOrderFillAmounts: BigNumber[];
|
||||||
|
sellAmount: BigNumber;
|
||||||
|
buyAmount: BigNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ABI-encode a `FillQuoteTransformer.TransformData` type.
|
||||||
|
*/
|
||||||
|
export function encodeFillQuoteTransformerData(data: FillQuoteTransformerData): string {
|
||||||
|
return fillQuoteTransformerDataEncoder.encode([data]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ABI encoder for `WethTransformer.TransformData`
|
||||||
|
*/
|
||||||
|
export const wethTransformerDataEncoder = AbiEncoder.create([
|
||||||
|
{
|
||||||
|
name: 'data',
|
||||||
|
type: 'tuple',
|
||||||
|
components: [{ name: 'token', type: 'address' }, { name: 'amount', type: 'uint256' }],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `WethTransformer.TransformData`
|
||||||
|
*/
|
||||||
|
export interface WethTransformerData {
|
||||||
|
token: string;
|
||||||
|
amount: BigNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ABI-encode a `WethTransformer.TransformData` type.
|
||||||
|
*/
|
||||||
|
export function encodeWethTransformerData(data: WethTransformerData): string {
|
||||||
|
return wethTransformerDataEncoder.encode([data]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ABI encoder for `PayTakerTransformer.TransformData`
|
||||||
|
*/
|
||||||
|
export const payTakerTransformerDataEncoder = AbiEncoder.create([
|
||||||
|
{
|
||||||
|
name: 'data',
|
||||||
|
type: 'tuple',
|
||||||
|
components: [{ name: 'tokens', type: 'address[]' }, { name: 'amounts', type: 'uint256[]' }],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `PayTakerTransformer.TransformData`
|
||||||
|
*/
|
||||||
|
export interface PayTakerTransformerData {
|
||||||
|
tokens: string[];
|
||||||
|
amounts: BigNumber[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ABI-encode a `PayTakerTransformer.TransformData` type.
|
||||||
|
*/
|
||||||
|
export function encodePayTakerTransformerData(data: PayTakerTransformerData): string {
|
||||||
|
return payTakerTransformerDataEncoder.encode([data]);
|
||||||
|
}
|
||||||
@@ -3,13 +3,16 @@
|
|||||||
* Warning: This file is auto-generated by contracts-gen. Don't edit manually.
|
* Warning: This file is auto-generated by contracts-gen. Don't edit manually.
|
||||||
* -----------------------------------------------------------------------------
|
* -----------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
|
export * from '../generated-wrappers/fill_quote_transformer';
|
||||||
export * from '../generated-wrappers/full_migration';
|
export * from '../generated-wrappers/full_migration';
|
||||||
export * from '../generated-wrappers/i_allowance_target';
|
|
||||||
export * from '../generated-wrappers/i_erc20_transformer';
|
export * from '../generated-wrappers/i_erc20_transformer';
|
||||||
export * from '../generated-wrappers/i_flash_wallet';
|
|
||||||
export * from '../generated-wrappers/i_ownable';
|
export * from '../generated-wrappers/i_ownable';
|
||||||
export * from '../generated-wrappers/i_simple_function_registry';
|
export * from '../generated-wrappers/i_simple_function_registry';
|
||||||
export * from '../generated-wrappers/i_token_spender';
|
export * from '../generated-wrappers/i_token_spender';
|
||||||
export * from '../generated-wrappers/i_transform_erc20';
|
export * from '../generated-wrappers/i_transform_erc20';
|
||||||
export * from '../generated-wrappers/initial_migration';
|
export * from '../generated-wrappers/initial_migration';
|
||||||
|
export * from '../generated-wrappers/lib_erc20_transformer';
|
||||||
|
export * from '../generated-wrappers/pay_taker_transformer';
|
||||||
|
export * from '../generated-wrappers/puppet';
|
||||||
|
export * from '../generated-wrappers/weth_transformer';
|
||||||
export * from '../generated-wrappers/zero_ex';
|
export * from '../generated-wrappers/zero_ex';
|
||||||
|
|||||||
@@ -7,16 +7,17 @@ import { ContractArtifact } from 'ethereum-types';
|
|||||||
|
|
||||||
import * as AllowanceTarget from '../test/generated-artifacts/AllowanceTarget.json';
|
import * as AllowanceTarget from '../test/generated-artifacts/AllowanceTarget.json';
|
||||||
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 FixinCommon from '../test/generated-artifacts/FixinCommon.json';
|
import * as FixinCommon from '../test/generated-artifacts/FixinCommon.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 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 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 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 IPuppet from '../test/generated-artifacts/IPuppet.json';
|
||||||
import * as ISimpleFunctionRegistry from '../test/generated-artifacts/ISimpleFunctionRegistry.json';
|
import * as ISimpleFunctionRegistry from '../test/generated-artifacts/ISimpleFunctionRegistry.json';
|
||||||
import * as ITestSimpleFunctionRegistryFeature from '../test/generated-artifacts/ITestSimpleFunctionRegistryFeature.json';
|
import * as ITestSimpleFunctionRegistryFeature from '../test/generated-artifacts/ITestSimpleFunctionRegistryFeature.json';
|
||||||
import * as ITokenSpender from '../test/generated-artifacts/ITokenSpender.json';
|
import * as ITokenSpender from '../test/generated-artifacts/ITokenSpender.json';
|
||||||
@@ -29,6 +30,7 @@ import * as LibOwnableRichErrors from '../test/generated-artifacts/LibOwnableRic
|
|||||||
import * as LibOwnableStorage from '../test/generated-artifacts/LibOwnableStorage.json';
|
import * as LibOwnableStorage from '../test/generated-artifacts/LibOwnableStorage.json';
|
||||||
import * as LibProxyRichErrors from '../test/generated-artifacts/LibProxyRichErrors.json';
|
import * as LibProxyRichErrors from '../test/generated-artifacts/LibProxyRichErrors.json';
|
||||||
import * as LibProxyStorage from '../test/generated-artifacts/LibProxyStorage.json';
|
import * as LibProxyStorage from '../test/generated-artifacts/LibProxyStorage.json';
|
||||||
|
import * as LibPuppetRichErrors from '../test/generated-artifacts/LibPuppetRichErrors.json';
|
||||||
import * as LibSimpleFunctionRegistryRichErrors from '../test/generated-artifacts/LibSimpleFunctionRegistryRichErrors.json';
|
import * as LibSimpleFunctionRegistryRichErrors from '../test/generated-artifacts/LibSimpleFunctionRegistryRichErrors.json';
|
||||||
import * as LibSimpleFunctionRegistryStorage from '../test/generated-artifacts/LibSimpleFunctionRegistryStorage.json';
|
import * as LibSimpleFunctionRegistryStorage from '../test/generated-artifacts/LibSimpleFunctionRegistryStorage.json';
|
||||||
import * as LibSpenderRichErrors from '../test/generated-artifacts/LibSpenderRichErrors.json';
|
import * as LibSpenderRichErrors from '../test/generated-artifacts/LibSpenderRichErrors.json';
|
||||||
@@ -36,37 +38,44 @@ import * as LibStorage from '../test/generated-artifacts/LibStorage.json';
|
|||||||
import * as LibTokenSpenderStorage from '../test/generated-artifacts/LibTokenSpenderStorage.json';
|
import * as LibTokenSpenderStorage from '../test/generated-artifacts/LibTokenSpenderStorage.json';
|
||||||
import * as LibTransformERC20RichErrors from '../test/generated-artifacts/LibTransformERC20RichErrors.json';
|
import * as LibTransformERC20RichErrors from '../test/generated-artifacts/LibTransformERC20RichErrors.json';
|
||||||
import * as LibTransformERC20Storage from '../test/generated-artifacts/LibTransformERC20Storage.json';
|
import * as LibTransformERC20Storage from '../test/generated-artifacts/LibTransformERC20Storage.json';
|
||||||
import * as LibWalletRichErrors from '../test/generated-artifacts/LibWalletRichErrors.json';
|
|
||||||
import * as Ownable from '../test/generated-artifacts/Ownable.json';
|
import * as Ownable from '../test/generated-artifacts/Ownable.json';
|
||||||
|
import * as PayTakerTransformer from '../test/generated-artifacts/PayTakerTransformer.json';
|
||||||
|
import * as Puppet from '../test/generated-artifacts/Puppet.json';
|
||||||
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 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';
|
import * as TestFullMigration from '../test/generated-artifacts/TestFullMigration.json';
|
||||||
import * as TestInitialMigration from '../test/generated-artifacts/TestInitialMigration.json';
|
import * as TestInitialMigration from '../test/generated-artifacts/TestInitialMigration.json';
|
||||||
import * as TestMigrator from '../test/generated-artifacts/TestMigrator.json';
|
import * as TestMigrator from '../test/generated-artifacts/TestMigrator.json';
|
||||||
import * as TestMintableERC20Token from '../test/generated-artifacts/TestMintableERC20Token.json';
|
import * as TestMintableERC20Token from '../test/generated-artifacts/TestMintableERC20Token.json';
|
||||||
import * as TestMintTokenERC20Transformer from '../test/generated-artifacts/TestMintTokenERC20Transformer.json';
|
import * as TestMintTokenERC20Transformer from '../test/generated-artifacts/TestMintTokenERC20Transformer.json';
|
||||||
|
import * as TestPuppetTarget from '../test/generated-artifacts/TestPuppetTarget.json';
|
||||||
import * as TestSimpleFunctionRegistryFeatureImpl1 from '../test/generated-artifacts/TestSimpleFunctionRegistryFeatureImpl1.json';
|
import * as TestSimpleFunctionRegistryFeatureImpl1 from '../test/generated-artifacts/TestSimpleFunctionRegistryFeatureImpl1.json';
|
||||||
import * as TestSimpleFunctionRegistryFeatureImpl2 from '../test/generated-artifacts/TestSimpleFunctionRegistryFeatureImpl2.json';
|
import * as TestSimpleFunctionRegistryFeatureImpl2 from '../test/generated-artifacts/TestSimpleFunctionRegistryFeatureImpl2.json';
|
||||||
import * as TestTokenSpender from '../test/generated-artifacts/TestTokenSpender.json';
|
import * as TestTokenSpender from '../test/generated-artifacts/TestTokenSpender.json';
|
||||||
import * as TestTokenSpenderERC20Token from '../test/generated-artifacts/TestTokenSpenderERC20Token.json';
|
import * as TestTokenSpenderERC20Token from '../test/generated-artifacts/TestTokenSpenderERC20Token.json';
|
||||||
import * as TestTransformERC20 from '../test/generated-artifacts/TestTransformERC20.json';
|
import * as TestTransformERC20 from '../test/generated-artifacts/TestTransformERC20.json';
|
||||||
|
import * as TestTransformerHost from '../test/generated-artifacts/TestTransformerHost.json';
|
||||||
|
import * as TestWeth from '../test/generated-artifacts/TestWeth.json';
|
||||||
|
import * as TestWethTransformerHost from '../test/generated-artifacts/TestWethTransformerHost.json';
|
||||||
import * as TestZeroExFeature from '../test/generated-artifacts/TestZeroExFeature.json';
|
import * as TestZeroExFeature from '../test/generated-artifacts/TestZeroExFeature.json';
|
||||||
import * as TokenSpender from '../test/generated-artifacts/TokenSpender.json';
|
import * as TokenSpender from '../test/generated-artifacts/TokenSpender.json';
|
||||||
import * as TransformERC20 from '../test/generated-artifacts/TransformERC20.json';
|
import * as TransformERC20 from '../test/generated-artifacts/TransformERC20.json';
|
||||||
|
import * as WethTransformer from '../test/generated-artifacts/WethTransformer.json';
|
||||||
import * as ZeroEx from '../test/generated-artifacts/ZeroEx.json';
|
import * as ZeroEx from '../test/generated-artifacts/ZeroEx.json';
|
||||||
export const artifacts = {
|
export const artifacts = {
|
||||||
ZeroEx: ZeroEx as ContractArtifact,
|
ZeroEx: ZeroEx as ContractArtifact,
|
||||||
LibCommonRichErrors: LibCommonRichErrors as ContractArtifact,
|
LibCommonRichErrors: LibCommonRichErrors as ContractArtifact,
|
||||||
LibOwnableRichErrors: LibOwnableRichErrors as ContractArtifact,
|
LibOwnableRichErrors: LibOwnableRichErrors as ContractArtifact,
|
||||||
LibProxyRichErrors: LibProxyRichErrors as ContractArtifact,
|
LibProxyRichErrors: LibProxyRichErrors as ContractArtifact,
|
||||||
|
LibPuppetRichErrors: LibPuppetRichErrors as ContractArtifact,
|
||||||
LibSimpleFunctionRegistryRichErrors: LibSimpleFunctionRegistryRichErrors as ContractArtifact,
|
LibSimpleFunctionRegistryRichErrors: LibSimpleFunctionRegistryRichErrors as ContractArtifact,
|
||||||
LibSpenderRichErrors: LibSpenderRichErrors as ContractArtifact,
|
LibSpenderRichErrors: LibSpenderRichErrors as ContractArtifact,
|
||||||
LibTransformERC20RichErrors: LibTransformERC20RichErrors as ContractArtifact,
|
LibTransformERC20RichErrors: LibTransformERC20RichErrors as ContractArtifact,
|
||||||
LibWalletRichErrors: LibWalletRichErrors as ContractArtifact,
|
|
||||||
AllowanceTarget: AllowanceTarget as ContractArtifact,
|
AllowanceTarget: AllowanceTarget as ContractArtifact,
|
||||||
FlashWallet: FlashWallet as ContractArtifact,
|
|
||||||
IAllowanceTarget: IAllowanceTarget as ContractArtifact,
|
IAllowanceTarget: IAllowanceTarget as ContractArtifact,
|
||||||
IFlashWallet: IFlashWallet as ContractArtifact,
|
IPuppet: IPuppet as ContractArtifact,
|
||||||
|
Puppet: Puppet as ContractArtifact,
|
||||||
Bootstrap: Bootstrap as ContractArtifact,
|
Bootstrap: Bootstrap as ContractArtifact,
|
||||||
IBootstrap: IBootstrap as ContractArtifact,
|
IBootstrap: IBootstrap as ContractArtifact,
|
||||||
IFeature: IFeature as ContractArtifact,
|
IFeature: IFeature as ContractArtifact,
|
||||||
@@ -89,19 +98,28 @@ export const artifacts = {
|
|||||||
LibStorage: LibStorage as ContractArtifact,
|
LibStorage: LibStorage as ContractArtifact,
|
||||||
LibTokenSpenderStorage: LibTokenSpenderStorage as ContractArtifact,
|
LibTokenSpenderStorage: LibTokenSpenderStorage as ContractArtifact,
|
||||||
LibTransformERC20Storage: LibTransformERC20Storage as ContractArtifact,
|
LibTransformERC20Storage: LibTransformERC20Storage as ContractArtifact,
|
||||||
|
FillQuoteTransformer: FillQuoteTransformer as ContractArtifact,
|
||||||
IERC20Transformer: IERC20Transformer as ContractArtifact,
|
IERC20Transformer: IERC20Transformer as ContractArtifact,
|
||||||
LibERC20Transformer: LibERC20Transformer as ContractArtifact,
|
LibERC20Transformer: LibERC20Transformer as ContractArtifact,
|
||||||
|
PayTakerTransformer: PayTakerTransformer as ContractArtifact,
|
||||||
|
WethTransformer: WethTransformer as ContractArtifact,
|
||||||
|
IExchange: IExchange as ContractArtifact,
|
||||||
ITestSimpleFunctionRegistryFeature: ITestSimpleFunctionRegistryFeature as ContractArtifact,
|
ITestSimpleFunctionRegistryFeature: ITestSimpleFunctionRegistryFeature as ContractArtifact,
|
||||||
TestCallTarget: TestCallTarget as ContractArtifact,
|
TestFillQuoteTransformerExchange: TestFillQuoteTransformerExchange as ContractArtifact,
|
||||||
|
TestFillQuoteTransformerHost: TestFillQuoteTransformerHost as ContractArtifact,
|
||||||
TestFullMigration: TestFullMigration as ContractArtifact,
|
TestFullMigration: TestFullMigration as ContractArtifact,
|
||||||
TestInitialMigration: TestInitialMigration as ContractArtifact,
|
TestInitialMigration: TestInitialMigration as ContractArtifact,
|
||||||
TestMigrator: TestMigrator as ContractArtifact,
|
TestMigrator: TestMigrator as ContractArtifact,
|
||||||
TestMintTokenERC20Transformer: TestMintTokenERC20Transformer as ContractArtifact,
|
TestMintTokenERC20Transformer: TestMintTokenERC20Transformer as ContractArtifact,
|
||||||
TestMintableERC20Token: TestMintableERC20Token as ContractArtifact,
|
TestMintableERC20Token: TestMintableERC20Token as ContractArtifact,
|
||||||
|
TestPuppetTarget: TestPuppetTarget as ContractArtifact,
|
||||||
TestSimpleFunctionRegistryFeatureImpl1: TestSimpleFunctionRegistryFeatureImpl1 as ContractArtifact,
|
TestSimpleFunctionRegistryFeatureImpl1: TestSimpleFunctionRegistryFeatureImpl1 as ContractArtifact,
|
||||||
TestSimpleFunctionRegistryFeatureImpl2: TestSimpleFunctionRegistryFeatureImpl2 as ContractArtifact,
|
TestSimpleFunctionRegistryFeatureImpl2: TestSimpleFunctionRegistryFeatureImpl2 as ContractArtifact,
|
||||||
TestTokenSpender: TestTokenSpender as ContractArtifact,
|
TestTokenSpender: TestTokenSpender as ContractArtifact,
|
||||||
TestTokenSpenderERC20Token: TestTokenSpenderERC20Token as ContractArtifact,
|
TestTokenSpenderERC20Token: TestTokenSpenderERC20Token as ContractArtifact,
|
||||||
TestTransformERC20: TestTransformERC20 as ContractArtifact,
|
TestTransformERC20: TestTransformERC20 as ContractArtifact,
|
||||||
|
TestTransformerHost: TestTransformerHost as ContractArtifact,
|
||||||
|
TestWeth: TestWeth as ContractArtifact,
|
||||||
|
TestWethTransformerHost: TestWethTransformerHost as ContractArtifact,
|
||||||
TestZeroExFeature: TestZeroExFeature as ContractArtifact,
|
TestZeroExFeature: TestZeroExFeature as ContractArtifact,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
} from '@0x/contracts-test-utils';
|
} from '@0x/contracts-test-utils';
|
||||||
import { AbiEncoder, hexUtils, ZeroExRevertErrors } from '@0x/utils';
|
import { AbiEncoder, hexUtils, ZeroExRevertErrors } from '@0x/utils';
|
||||||
|
|
||||||
|
import { ETH_TOKEN_ADDRESS } from '../../src/constants';
|
||||||
import { getRLPEncodedAccountNonceAsync } from '../../src/nonce_utils';
|
import { getRLPEncodedAccountNonceAsync } from '../../src/nonce_utils';
|
||||||
import { artifacts } from '../artifacts';
|
import { artifacts } from '../artifacts';
|
||||||
import { abis } from '../utils/abis';
|
import { abis } from '../utils/abis';
|
||||||
@@ -206,8 +207,6 @@ blockchainTests.resets('TransformERC20 feature', env => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const ETH_TOKEN_ADDRESS = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee';
|
|
||||||
|
|
||||||
it("succeeds if taker's output token balance increases by exactly minOutputTokenAmount, with ETH", async () => {
|
it("succeeds if taker's output token balance increases by exactly minOutputTokenAmount, with ETH", async () => {
|
||||||
const startingInputTokenBalance = getRandomInteger(0, '100e18');
|
const startingInputTokenBalance = getRandomInteger(0, '100e18');
|
||||||
await inputToken.mint(taker, startingInputTokenBalance).awaitTransactionSuccessAsync();
|
await inputToken.mint(taker, startingInputTokenBalance).awaitTransactionSuccessAsync();
|
||||||
|
|||||||
@@ -0,0 +1,849 @@
|
|||||||
|
import {
|
||||||
|
assertIntegerRoughlyEquals,
|
||||||
|
blockchainTests,
|
||||||
|
constants,
|
||||||
|
expect,
|
||||||
|
getRandomInteger,
|
||||||
|
Numberish,
|
||||||
|
randomAddress,
|
||||||
|
} from '@0x/contracts-test-utils';
|
||||||
|
import { assetDataUtils } from '@0x/order-utils';
|
||||||
|
import { Order } from '@0x/types';
|
||||||
|
import { BigNumber, hexUtils, ZeroExRevertErrors } from '@0x/utils';
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
|
import { encodeFillQuoteTransformerData, FillQuoteTransformerData } from '../../src/transformer_data_encoders';
|
||||||
|
import { artifacts } from '../artifacts';
|
||||||
|
import {
|
||||||
|
FillQuoteTransformerContract,
|
||||||
|
TestFillQuoteTransformerExchangeContract,
|
||||||
|
TestFillQuoteTransformerHostContract,
|
||||||
|
TestMintableERC20TokenContract,
|
||||||
|
} from '../wrappers';
|
||||||
|
|
||||||
|
const { NULL_ADDRESS, NULL_BYTES, MAX_UINT256, ZERO_AMOUNT } = constants;
|
||||||
|
|
||||||
|
blockchainTests.resets('FillQuoteTransformer', env => {
|
||||||
|
let maker: string;
|
||||||
|
let feeRecipient: string;
|
||||||
|
let exchange: TestFillQuoteTransformerExchangeContract;
|
||||||
|
let transformer: FillQuoteTransformerContract;
|
||||||
|
let host: TestFillQuoteTransformerHostContract;
|
||||||
|
let makerToken: TestMintableERC20TokenContract;
|
||||||
|
let takerToken: TestMintableERC20TokenContract;
|
||||||
|
let takerFeeToken: TestMintableERC20TokenContract;
|
||||||
|
let singleProtocolFee: BigNumber;
|
||||||
|
|
||||||
|
const GAS_PRICE = 1337;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
[maker, feeRecipient] = await env.getAccountAddressesAsync();
|
||||||
|
exchange = await TestFillQuoteTransformerExchangeContract.deployFrom0xArtifactAsync(
|
||||||
|
artifacts.TestFillQuoteTransformerExchange,
|
||||||
|
env.provider,
|
||||||
|
env.txDefaults,
|
||||||
|
artifacts,
|
||||||
|
);
|
||||||
|
transformer = await FillQuoteTransformerContract.deployFrom0xArtifactAsync(
|
||||||
|
artifacts.FillQuoteTransformer,
|
||||||
|
env.provider,
|
||||||
|
env.txDefaults,
|
||||||
|
artifacts,
|
||||||
|
exchange.address,
|
||||||
|
);
|
||||||
|
host = await TestFillQuoteTransformerHostContract.deployFrom0xArtifactAsync(
|
||||||
|
artifacts.TestFillQuoteTransformerHost,
|
||||||
|
env.provider,
|
||||||
|
{
|
||||||
|
...env.txDefaults,
|
||||||
|
gasPrice: GAS_PRICE,
|
||||||
|
},
|
||||||
|
artifacts,
|
||||||
|
);
|
||||||
|
[makerToken, takerToken, takerFeeToken] = await Promise.all(
|
||||||
|
_.times(3, async () =>
|
||||||
|
TestMintableERC20TokenContract.deployFrom0xArtifactAsync(
|
||||||
|
artifacts.TestMintableERC20Token,
|
||||||
|
env.provider,
|
||||||
|
env.txDefaults,
|
||||||
|
artifacts,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
singleProtocolFee = (await exchange.protocolFeeMultiplier().callAsync()).times(GAS_PRICE);
|
||||||
|
});
|
||||||
|
|
||||||
|
type FilledOrder = Order & { filledTakerAssetAmount: BigNumber };
|
||||||
|
|
||||||
|
function createOrder(fields: Partial<Order> = {}): FilledOrder {
|
||||||
|
return {
|
||||||
|
chainId: 1,
|
||||||
|
exchangeAddress: exchange.address,
|
||||||
|
expirationTimeSeconds: ZERO_AMOUNT,
|
||||||
|
salt: ZERO_AMOUNT,
|
||||||
|
senderAddress: NULL_ADDRESS,
|
||||||
|
takerAddress: NULL_ADDRESS,
|
||||||
|
makerAddress: maker,
|
||||||
|
feeRecipientAddress: feeRecipient,
|
||||||
|
makerAssetAmount: getRandomInteger('0.1e18', '1e18'),
|
||||||
|
takerAssetAmount: getRandomInteger('0.1e18', '1e18'),
|
||||||
|
makerFee: ZERO_AMOUNT,
|
||||||
|
takerFee: getRandomInteger('0.001e18', '0.1e18'),
|
||||||
|
makerAssetData: assetDataUtils.encodeERC20AssetData(makerToken.address),
|
||||||
|
takerAssetData: assetDataUtils.encodeERC20AssetData(takerToken.address),
|
||||||
|
makerFeeAssetData: NULL_BYTES,
|
||||||
|
takerFeeAssetData: assetDataUtils.encodeERC20AssetData(takerToken.address),
|
||||||
|
filledTakerAssetAmount: ZERO_AMOUNT,
|
||||||
|
...fields,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface QuoteFillResults {
|
||||||
|
makerAssetBought: BigNumber;
|
||||||
|
takerAssetSpent: BigNumber;
|
||||||
|
protocolFeePaid: BigNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ZERO_QUOTE_FILL_RESULTS = {
|
||||||
|
makerAssetBought: ZERO_AMOUNT,
|
||||||
|
takerAssetSpent: ZERO_AMOUNT,
|
||||||
|
protocolFeePaid: ZERO_AMOUNT,
|
||||||
|
};
|
||||||
|
|
||||||
|
function getExpectedSellQuoteFillResults(
|
||||||
|
orders: FilledOrder[],
|
||||||
|
takerAssetFillAmount: BigNumber = constants.MAX_UINT256,
|
||||||
|
): QuoteFillResults {
|
||||||
|
const qfr = { ...ZERO_QUOTE_FILL_RESULTS };
|
||||||
|
for (const order of orders) {
|
||||||
|
if (qfr.takerAssetSpent.gte(takerAssetFillAmount)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const singleFillAmount = BigNumber.min(
|
||||||
|
takerAssetFillAmount.minus(qfr.takerAssetSpent),
|
||||||
|
order.takerAssetAmount.minus(order.filledTakerAssetAmount),
|
||||||
|
);
|
||||||
|
const fillRatio = singleFillAmount.div(order.takerAssetAmount);
|
||||||
|
qfr.takerAssetSpent = qfr.takerAssetSpent.plus(singleFillAmount);
|
||||||
|
qfr.protocolFeePaid = qfr.protocolFeePaid.plus(singleProtocolFee);
|
||||||
|
qfr.makerAssetBought = qfr.makerAssetBought.plus(
|
||||||
|
fillRatio.times(order.makerAssetAmount).integerValue(BigNumber.ROUND_DOWN),
|
||||||
|
);
|
||||||
|
const takerFee = fillRatio.times(order.takerFee).integerValue(BigNumber.ROUND_DOWN);
|
||||||
|
if (order.takerAssetData === order.takerFeeAssetData) {
|
||||||
|
// Taker fee is in taker asset.
|
||||||
|
qfr.takerAssetSpent = qfr.takerAssetSpent.plus(takerFee);
|
||||||
|
} else if (order.makerAssetData === order.takerFeeAssetData) {
|
||||||
|
// Taker fee is in maker asset.
|
||||||
|
qfr.makerAssetBought = qfr.makerAssetBought.minus(takerFee);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return qfr;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getExpectedBuyQuoteFillResults(
|
||||||
|
orders: FilledOrder[],
|
||||||
|
makerAssetFillAmount: BigNumber = constants.MAX_UINT256,
|
||||||
|
): QuoteFillResults {
|
||||||
|
const qfr = { ...ZERO_QUOTE_FILL_RESULTS };
|
||||||
|
for (const order of orders) {
|
||||||
|
if (qfr.makerAssetBought.gte(makerAssetFillAmount)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const filledMakerAssetAmount = order.filledTakerAssetAmount
|
||||||
|
.times(order.makerAssetAmount.div(order.takerAssetAmount))
|
||||||
|
.integerValue(BigNumber.ROUND_DOWN);
|
||||||
|
const singleFillAmount = BigNumber.min(
|
||||||
|
makerAssetFillAmount.minus(qfr.makerAssetBought),
|
||||||
|
order.makerAssetAmount.minus(filledMakerAssetAmount),
|
||||||
|
);
|
||||||
|
const fillRatio = singleFillAmount.div(order.makerAssetAmount);
|
||||||
|
qfr.takerAssetSpent = qfr.takerAssetSpent.plus(
|
||||||
|
fillRatio.times(order.takerAssetAmount).integerValue(BigNumber.ROUND_UP),
|
||||||
|
);
|
||||||
|
qfr.protocolFeePaid = qfr.protocolFeePaid.plus(singleProtocolFee);
|
||||||
|
qfr.makerAssetBought = qfr.makerAssetBought.plus(singleFillAmount);
|
||||||
|
const takerFee = fillRatio.times(order.takerFee).integerValue(BigNumber.ROUND_UP);
|
||||||
|
if (order.takerAssetData === order.takerFeeAssetData) {
|
||||||
|
// Taker fee is in taker asset.
|
||||||
|
qfr.takerAssetSpent = qfr.takerAssetSpent.plus(takerFee);
|
||||||
|
} else if (order.makerAssetData === order.takerFeeAssetData) {
|
||||||
|
// Taker fee is in maker asset.
|
||||||
|
qfr.makerAssetBought = qfr.makerAssetBought.minus(takerFee);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return qfr;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Balances {
|
||||||
|
makerAssetBalance: BigNumber;
|
||||||
|
takerAssetBalance: BigNumber;
|
||||||
|
takerFeeBalance: BigNumber;
|
||||||
|
protocolFeeBalance: BigNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ZERO_BALANCES = {
|
||||||
|
makerAssetBalance: ZERO_AMOUNT,
|
||||||
|
takerAssetBalance: ZERO_AMOUNT,
|
||||||
|
takerFeeBalance: ZERO_AMOUNT,
|
||||||
|
protocolFeeBalance: ZERO_AMOUNT,
|
||||||
|
};
|
||||||
|
|
||||||
|
async function getBalancesAsync(owner: string): Promise<Balances> {
|
||||||
|
const balances = { ...ZERO_BALANCES };
|
||||||
|
[
|
||||||
|
balances.makerAssetBalance,
|
||||||
|
balances.takerAssetBalance,
|
||||||
|
balances.takerFeeBalance,
|
||||||
|
balances.protocolFeeBalance,
|
||||||
|
] = await Promise.all([
|
||||||
|
makerToken.balanceOf(owner).callAsync(),
|
||||||
|
takerToken.balanceOf(owner).callAsync(),
|
||||||
|
takerFeeToken.balanceOf(owner).callAsync(),
|
||||||
|
env.web3Wrapper.getBalanceInWeiAsync(owner),
|
||||||
|
]);
|
||||||
|
return balances;
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertBalances(actual: Balances, expected: Balances): void {
|
||||||
|
assertIntegerRoughlyEquals(actual.makerAssetBalance, expected.makerAssetBalance, 10, 'makerAssetBalance');
|
||||||
|
assertIntegerRoughlyEquals(actual.takerAssetBalance, expected.takerAssetBalance, 10, 'takerAssetBalance');
|
||||||
|
assertIntegerRoughlyEquals(actual.takerFeeBalance, expected.takerFeeBalance, 10, 'takerFeeBalance');
|
||||||
|
assertIntegerRoughlyEquals(actual.protocolFeeBalance, expected.protocolFeeBalance, 10, 'protocolFeeBalance');
|
||||||
|
}
|
||||||
|
|
||||||
|
function encodeTransformData(fields: Partial<FillQuoteTransformerData> = {}): string {
|
||||||
|
return encodeFillQuoteTransformerData({
|
||||||
|
sellToken: takerToken.address,
|
||||||
|
buyToken: makerToken.address,
|
||||||
|
orders: [],
|
||||||
|
signatures: [],
|
||||||
|
maxOrderFillAmounts: [],
|
||||||
|
sellAmount: MAX_UINT256,
|
||||||
|
buyAmount: ZERO_AMOUNT,
|
||||||
|
...fields,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function encodeExchangeBehavior(
|
||||||
|
filledTakerAssetAmount: Numberish = 0,
|
||||||
|
makerAssetMintRatio: Numberish = 1.0,
|
||||||
|
): string {
|
||||||
|
return hexUtils.slice(
|
||||||
|
exchange
|
||||||
|
.encodeBehaviorData({
|
||||||
|
filledTakerAssetAmount: new BigNumber(filledTakerAssetAmount),
|
||||||
|
makerAssetMintRatio: new BigNumber(makerAssetMintRatio).times('1e18').integerValue(),
|
||||||
|
})
|
||||||
|
.getABIEncodedTransactionData(),
|
||||||
|
4,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ERC20_ASSET_PROXY_ID = '0xf47261b0';
|
||||||
|
|
||||||
|
describe('sell quotes', () => {
|
||||||
|
it('can fully sell to a single order quote', async () => {
|
||||||
|
const orders = _.times(1, () => createOrder());
|
||||||
|
const signatures = orders.map(() => encodeExchangeBehavior());
|
||||||
|
const qfr = getExpectedSellQuoteFillResults(orders);
|
||||||
|
await host
|
||||||
|
.executeTransform(
|
||||||
|
transformer.address,
|
||||||
|
takerToken.address,
|
||||||
|
qfr.takerAssetSpent,
|
||||||
|
encodeTransformData({
|
||||||
|
orders,
|
||||||
|
signatures,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid });
|
||||||
|
assertBalances(await getBalancesAsync(host.address), {
|
||||||
|
...ZERO_BALANCES,
|
||||||
|
makerAssetBalance: qfr.makerAssetBought,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can fully sell to multi order quote', async () => {
|
||||||
|
const orders = _.times(3, () => createOrder());
|
||||||
|
const signatures = orders.map(() => encodeExchangeBehavior());
|
||||||
|
const qfr = getExpectedSellQuoteFillResults(orders);
|
||||||
|
await host
|
||||||
|
.executeTransform(
|
||||||
|
transformer.address,
|
||||||
|
takerToken.address,
|
||||||
|
qfr.takerAssetSpent,
|
||||||
|
encodeTransformData({
|
||||||
|
orders,
|
||||||
|
signatures,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid });
|
||||||
|
assertBalances(await getBalancesAsync(host.address), {
|
||||||
|
...ZERO_BALANCES,
|
||||||
|
makerAssetBalance: qfr.makerAssetBought,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can partially sell to single order quote', async () => {
|
||||||
|
const orders = _.times(1, () => createOrder());
|
||||||
|
const signatures = orders.map(() => encodeExchangeBehavior());
|
||||||
|
const qfr = getExpectedSellQuoteFillResults(
|
||||||
|
orders,
|
||||||
|
getExpectedSellQuoteFillResults(orders).takerAssetSpent.dividedToIntegerBy(2),
|
||||||
|
);
|
||||||
|
await host
|
||||||
|
.executeTransform(
|
||||||
|
transformer.address,
|
||||||
|
takerToken.address,
|
||||||
|
qfr.takerAssetSpent,
|
||||||
|
encodeTransformData({
|
||||||
|
orders,
|
||||||
|
signatures,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid });
|
||||||
|
assertBalances(await getBalancesAsync(host.address), {
|
||||||
|
...ZERO_BALANCES,
|
||||||
|
makerAssetBalance: qfr.makerAssetBought,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can partially sell to multi order quote and refund unused protocol fees', async () => {
|
||||||
|
const orders = _.times(3, () => createOrder());
|
||||||
|
const signatures = orders.map(() => encodeExchangeBehavior());
|
||||||
|
const qfr = getExpectedSellQuoteFillResults(orders.slice(0, 2));
|
||||||
|
const maxProtocolFees = singleProtocolFee.times(orders.length);
|
||||||
|
await host
|
||||||
|
.executeTransform(
|
||||||
|
transformer.address,
|
||||||
|
takerToken.address,
|
||||||
|
qfr.takerAssetSpent,
|
||||||
|
encodeTransformData({
|
||||||
|
orders,
|
||||||
|
signatures,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.awaitTransactionSuccessAsync({ value: maxProtocolFees });
|
||||||
|
assertBalances(await getBalancesAsync(host.address), {
|
||||||
|
...ZERO_BALANCES,
|
||||||
|
makerAssetBalance: qfr.makerAssetBought,
|
||||||
|
protocolFeeBalance: singleProtocolFee,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can sell to multi order quote with a failing order', async () => {
|
||||||
|
const orders = _.times(3, () => createOrder());
|
||||||
|
// First order will fail.
|
||||||
|
const validOrders = orders.slice(1);
|
||||||
|
const signatures = [NULL_BYTES, ...validOrders.map(() => encodeExchangeBehavior())];
|
||||||
|
const qfr = getExpectedSellQuoteFillResults(validOrders);
|
||||||
|
await host
|
||||||
|
.executeTransform(
|
||||||
|
transformer.address,
|
||||||
|
takerToken.address,
|
||||||
|
qfr.takerAssetSpent,
|
||||||
|
encodeTransformData({
|
||||||
|
orders,
|
||||||
|
signatures,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid });
|
||||||
|
assertBalances(await getBalancesAsync(host.address), {
|
||||||
|
...ZERO_BALANCES,
|
||||||
|
makerAssetBalance: qfr.makerAssetBought,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('succeeds if an order transfers too few maker tokens', async () => {
|
||||||
|
const mintScale = 0.5;
|
||||||
|
const orders = _.times(3, () => createOrder());
|
||||||
|
// First order mints less than expected.
|
||||||
|
const signatures = [
|
||||||
|
encodeExchangeBehavior(0, mintScale),
|
||||||
|
...orders.slice(1).map(() => encodeExchangeBehavior()),
|
||||||
|
];
|
||||||
|
const qfr = getExpectedSellQuoteFillResults(orders);
|
||||||
|
await host
|
||||||
|
.executeTransform(
|
||||||
|
transformer.address,
|
||||||
|
takerToken.address,
|
||||||
|
qfr.takerAssetSpent,
|
||||||
|
encodeTransformData({
|
||||||
|
orders,
|
||||||
|
signatures,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid });
|
||||||
|
assertBalances(await getBalancesAsync(host.address), {
|
||||||
|
...ZERO_BALANCES,
|
||||||
|
makerAssetBalance: qfr.makerAssetBought
|
||||||
|
.minus(orders[0].makerAssetAmount.times(1 - mintScale))
|
||||||
|
.integerValue(BigNumber.ROUND_DOWN),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can fail if an order is partially filled', async () => {
|
||||||
|
const orders = _.times(3, () => createOrder());
|
||||||
|
// First order is partially filled.
|
||||||
|
const filledOrder = {
|
||||||
|
...orders[0],
|
||||||
|
filledTakerAssetAmount: orders[0].takerAssetAmount.dividedToIntegerBy(2),
|
||||||
|
};
|
||||||
|
// First order is partially filled.
|
||||||
|
const signatures = [
|
||||||
|
encodeExchangeBehavior(filledOrder.filledTakerAssetAmount),
|
||||||
|
...orders.slice(1).map(() => encodeExchangeBehavior()),
|
||||||
|
];
|
||||||
|
const qfr = getExpectedSellQuoteFillResults(orders);
|
||||||
|
const tx = host
|
||||||
|
.executeTransform(
|
||||||
|
transformer.address,
|
||||||
|
takerToken.address,
|
||||||
|
qfr.takerAssetSpent,
|
||||||
|
encodeTransformData({
|
||||||
|
orders,
|
||||||
|
signatures,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid });
|
||||||
|
return expect(tx).to.revertWith(
|
||||||
|
new ZeroExRevertErrors.TransformERC20.IncompleteFillSellQuoteError(
|
||||||
|
takerToken.address,
|
||||||
|
getExpectedSellQuoteFillResults([filledOrder, ...orders.slice(1)]).takerAssetSpent,
|
||||||
|
qfr.takerAssetSpent,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fails if not enough protocol fee provided', async () => {
|
||||||
|
const orders = _.times(3, () => createOrder());
|
||||||
|
const signatures = orders.map(() => encodeExchangeBehavior());
|
||||||
|
const qfr = getExpectedSellQuoteFillResults(orders);
|
||||||
|
const tx = host
|
||||||
|
.executeTransform(
|
||||||
|
transformer.address,
|
||||||
|
takerToken.address,
|
||||||
|
qfr.takerAssetSpent,
|
||||||
|
encodeTransformData({
|
||||||
|
orders,
|
||||||
|
signatures,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid.minus(1) });
|
||||||
|
return expect(tx).to.revertWith(
|
||||||
|
new ZeroExRevertErrors.TransformERC20.InsufficientProtocolFeeError(
|
||||||
|
singleProtocolFee.minus(1),
|
||||||
|
singleProtocolFee,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can sell less than the taker token balance', async () => {
|
||||||
|
const orders = _.times(3, () => createOrder());
|
||||||
|
const signatures = orders.map(() => encodeExchangeBehavior());
|
||||||
|
const qfr = getExpectedSellQuoteFillResults(orders);
|
||||||
|
const takerTokenBalance = qfr.takerAssetSpent.times(1.01).integerValue();
|
||||||
|
await host
|
||||||
|
.executeTransform(
|
||||||
|
transformer.address,
|
||||||
|
takerToken.address,
|
||||||
|
takerTokenBalance,
|
||||||
|
encodeTransformData({
|
||||||
|
orders,
|
||||||
|
signatures,
|
||||||
|
sellAmount: qfr.takerAssetSpent,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid });
|
||||||
|
assertBalances(await getBalancesAsync(host.address), {
|
||||||
|
...ZERO_BALANCES,
|
||||||
|
makerAssetBalance: qfr.makerAssetBought,
|
||||||
|
takerAssetBalance: qfr.takerAssetSpent.times(0.01).integerValue(),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fails to sell more than the taker token balance', async () => {
|
||||||
|
const orders = _.times(3, () => createOrder());
|
||||||
|
const signatures = orders.map(() => encodeExchangeBehavior());
|
||||||
|
const qfr = getExpectedSellQuoteFillResults(orders);
|
||||||
|
const takerTokenBalance = qfr.takerAssetSpent.times(0.99).integerValue();
|
||||||
|
const tx = host
|
||||||
|
.executeTransform(
|
||||||
|
transformer.address,
|
||||||
|
takerToken.address,
|
||||||
|
takerTokenBalance,
|
||||||
|
encodeTransformData({
|
||||||
|
orders,
|
||||||
|
signatures,
|
||||||
|
sellAmount: qfr.takerAssetSpent,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid });
|
||||||
|
return expect(tx).to.revertWith(
|
||||||
|
new ZeroExRevertErrors.TransformERC20.IncompleteFillSellQuoteError(
|
||||||
|
takerToken.address,
|
||||||
|
getExpectedSellQuoteFillResults(orders.slice(0, 2)).takerAssetSpent,
|
||||||
|
qfr.takerAssetSpent,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can fully sell to a single order with maker asset taker fees', async () => {
|
||||||
|
const orders = _.times(1, () =>
|
||||||
|
createOrder({
|
||||||
|
takerFeeAssetData: assetDataUtils.encodeERC20AssetData(makerToken.address),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
const signatures = orders.map(() => encodeExchangeBehavior());
|
||||||
|
const qfr = getExpectedSellQuoteFillResults(orders);
|
||||||
|
await host
|
||||||
|
.executeTransform(
|
||||||
|
transformer.address,
|
||||||
|
takerToken.address,
|
||||||
|
qfr.takerAssetSpent,
|
||||||
|
encodeTransformData({
|
||||||
|
orders,
|
||||||
|
signatures,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid });
|
||||||
|
assertBalances(await getBalancesAsync(host.address), {
|
||||||
|
...ZERO_BALANCES,
|
||||||
|
makerAssetBalance: qfr.makerAssetBought,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fails if an order has a non-standard taker fee asset', async () => {
|
||||||
|
const BAD_ASSET_DATA = hexUtils.random(36);
|
||||||
|
const orders = _.times(1, () => createOrder({ takerFeeAssetData: BAD_ASSET_DATA }));
|
||||||
|
const signatures = orders.map(() => encodeExchangeBehavior());
|
||||||
|
const qfr = getExpectedSellQuoteFillResults(orders);
|
||||||
|
const tx = host
|
||||||
|
.executeTransform(
|
||||||
|
transformer.address,
|
||||||
|
takerToken.address,
|
||||||
|
qfr.takerAssetSpent,
|
||||||
|
encodeTransformData({
|
||||||
|
orders,
|
||||||
|
signatures,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid });
|
||||||
|
return expect(tx).to.revertWith(
|
||||||
|
new ZeroExRevertErrors.TransformERC20.InvalidERC20AssetDataError(BAD_ASSET_DATA),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fails if an order has a fee asset that is neither maker or taker asset', async () => {
|
||||||
|
const badToken = randomAddress();
|
||||||
|
const BAD_ASSET_DATA = hexUtils.concat(ERC20_ASSET_PROXY_ID, hexUtils.leftPad(badToken));
|
||||||
|
const orders = _.times(1, () => createOrder({ takerFeeAssetData: BAD_ASSET_DATA }));
|
||||||
|
const signatures = orders.map(() => encodeExchangeBehavior());
|
||||||
|
const qfr = getExpectedSellQuoteFillResults(orders);
|
||||||
|
const tx = host
|
||||||
|
.executeTransform(
|
||||||
|
transformer.address,
|
||||||
|
takerToken.address,
|
||||||
|
qfr.takerAssetSpent,
|
||||||
|
encodeTransformData({
|
||||||
|
orders,
|
||||||
|
signatures,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid });
|
||||||
|
return expect(tx).to.revertWith(new ZeroExRevertErrors.TransformERC20.InvalidTakerFeeTokenError(badToken));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('respects `maxOrderFillAmounts`', async () => {
|
||||||
|
const orders = _.times(2, () => createOrder());
|
||||||
|
const signatures = orders.map(() => encodeExchangeBehavior());
|
||||||
|
const qfr = getExpectedSellQuoteFillResults(orders.slice(1));
|
||||||
|
const protocolFee = singleProtocolFee.times(2);
|
||||||
|
await host
|
||||||
|
.executeTransform(
|
||||||
|
transformer.address,
|
||||||
|
takerToken.address,
|
||||||
|
qfr.takerAssetSpent,
|
||||||
|
encodeTransformData({
|
||||||
|
orders,
|
||||||
|
signatures,
|
||||||
|
// Skip the first order.
|
||||||
|
maxOrderFillAmounts: [ZERO_AMOUNT],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.awaitTransactionSuccessAsync({ value: protocolFee });
|
||||||
|
assertBalances(await getBalancesAsync(host.address), {
|
||||||
|
...ZERO_BALANCES,
|
||||||
|
makerAssetBalance: qfr.makerAssetBought,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('buy quotes', () => {
|
||||||
|
it('can fully buy from a single order quote', async () => {
|
||||||
|
const orders = _.times(1, () => createOrder());
|
||||||
|
const signatures = orders.map(() => encodeExchangeBehavior());
|
||||||
|
const qfr = getExpectedBuyQuoteFillResults(orders);
|
||||||
|
await host
|
||||||
|
.executeTransform(
|
||||||
|
transformer.address,
|
||||||
|
takerToken.address,
|
||||||
|
qfr.takerAssetSpent,
|
||||||
|
encodeTransformData({
|
||||||
|
orders,
|
||||||
|
signatures,
|
||||||
|
buyAmount: qfr.makerAssetBought,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid });
|
||||||
|
assertBalances(await getBalancesAsync(host.address), {
|
||||||
|
...ZERO_BALANCES,
|
||||||
|
makerAssetBalance: qfr.makerAssetBought,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can fully buy from a multi order quote', async () => {
|
||||||
|
const orders = _.times(3, () => createOrder());
|
||||||
|
const signatures = orders.map(() => encodeExchangeBehavior());
|
||||||
|
const qfr = getExpectedBuyQuoteFillResults(orders);
|
||||||
|
await host
|
||||||
|
.executeTransform(
|
||||||
|
transformer.address,
|
||||||
|
takerToken.address,
|
||||||
|
qfr.takerAssetSpent,
|
||||||
|
encodeTransformData({
|
||||||
|
orders,
|
||||||
|
signatures,
|
||||||
|
buyAmount: qfr.makerAssetBought,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid });
|
||||||
|
assertBalances(await getBalancesAsync(host.address), {
|
||||||
|
...ZERO_BALANCES,
|
||||||
|
makerAssetBalance: qfr.makerAssetBought,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can partially buy from a single order quote', async () => {
|
||||||
|
const orders = _.times(1, () => createOrder());
|
||||||
|
const signatures = orders.map(() => encodeExchangeBehavior());
|
||||||
|
const qfr = getExpectedBuyQuoteFillResults(
|
||||||
|
orders,
|
||||||
|
getExpectedBuyQuoteFillResults(orders).makerAssetBought.dividedToIntegerBy(2),
|
||||||
|
);
|
||||||
|
await host
|
||||||
|
.executeTransform(
|
||||||
|
transformer.address,
|
||||||
|
takerToken.address,
|
||||||
|
qfr.takerAssetSpent,
|
||||||
|
encodeTransformData({
|
||||||
|
orders,
|
||||||
|
signatures,
|
||||||
|
buyAmount: qfr.makerAssetBought,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid });
|
||||||
|
assertBalances(await getBalancesAsync(host.address), {
|
||||||
|
...ZERO_BALANCES,
|
||||||
|
makerAssetBalance: qfr.makerAssetBought,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can partially buy from multi order quote and refund unused protocol fees', async () => {
|
||||||
|
const orders = _.times(3, () => createOrder());
|
||||||
|
const signatures = orders.map(() => encodeExchangeBehavior());
|
||||||
|
const qfr = getExpectedBuyQuoteFillResults(orders.slice(0, 2));
|
||||||
|
const maxProtocolFees = singleProtocolFee.times(orders.length);
|
||||||
|
await host
|
||||||
|
.executeTransform(
|
||||||
|
transformer.address,
|
||||||
|
takerToken.address,
|
||||||
|
qfr.takerAssetSpent,
|
||||||
|
encodeTransformData({
|
||||||
|
orders,
|
||||||
|
signatures,
|
||||||
|
buyAmount: qfr.makerAssetBought,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.awaitTransactionSuccessAsync({ value: maxProtocolFees });
|
||||||
|
assertBalances(await getBalancesAsync(host.address), {
|
||||||
|
...ZERO_BALANCES,
|
||||||
|
makerAssetBalance: qfr.makerAssetBought,
|
||||||
|
protocolFeeBalance: singleProtocolFee,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can buy from multi order quote with a failing order', async () => {
|
||||||
|
const orders = _.times(3, () => createOrder());
|
||||||
|
// First order will fail.
|
||||||
|
const validOrders = orders.slice(1);
|
||||||
|
const signatures = [NULL_BYTES, ...validOrders.map(() => encodeExchangeBehavior())];
|
||||||
|
const qfr = getExpectedBuyQuoteFillResults(validOrders);
|
||||||
|
await host
|
||||||
|
.executeTransform(
|
||||||
|
transformer.address,
|
||||||
|
takerToken.address,
|
||||||
|
qfr.takerAssetSpent,
|
||||||
|
encodeTransformData({
|
||||||
|
orders,
|
||||||
|
signatures,
|
||||||
|
buyAmount: qfr.makerAssetBought,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid });
|
||||||
|
assertBalances(await getBalancesAsync(host.address), {
|
||||||
|
...ZERO_BALANCES,
|
||||||
|
makerAssetBalance: qfr.makerAssetBought,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
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,
|
||||||
|
buyAmount: 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());
|
||||||
|
const qfr = getExpectedBuyQuoteFillResults(orders);
|
||||||
|
const tx = host
|
||||||
|
.executeTransform(
|
||||||
|
transformer.address,
|
||||||
|
takerToken.address,
|
||||||
|
qfr.takerAssetSpent,
|
||||||
|
encodeTransformData({
|
||||||
|
orders,
|
||||||
|
signatures,
|
||||||
|
buyAmount: qfr.makerAssetBought.plus(1),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid });
|
||||||
|
return expect(tx).to.revertWith(
|
||||||
|
new ZeroExRevertErrors.TransformERC20.IncompleteFillBuyQuoteError(
|
||||||
|
makerToken.address,
|
||||||
|
qfr.makerAssetBought,
|
||||||
|
qfr.makerAssetBought.plus(1),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can fully buy from a single order with maker asset taker fees', async () => {
|
||||||
|
const orders = _.times(1, () =>
|
||||||
|
createOrder({
|
||||||
|
takerFeeAssetData: assetDataUtils.encodeERC20AssetData(makerToken.address),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
const signatures = orders.map(() => encodeExchangeBehavior());
|
||||||
|
const qfr = getExpectedBuyQuoteFillResults(orders);
|
||||||
|
await host
|
||||||
|
.executeTransform(
|
||||||
|
transformer.address,
|
||||||
|
takerToken.address,
|
||||||
|
qfr.takerAssetSpent,
|
||||||
|
encodeTransformData({
|
||||||
|
orders,
|
||||||
|
signatures,
|
||||||
|
buyAmount: qfr.makerAssetBought,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid });
|
||||||
|
assertBalances(await getBalancesAsync(host.address), {
|
||||||
|
...ZERO_BALANCES,
|
||||||
|
makerAssetBalance: qfr.makerAssetBought,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fails if an order has a non-standard taker fee asset', async () => {
|
||||||
|
const BAD_ASSET_DATA = hexUtils.random(36);
|
||||||
|
const orders = _.times(1, () => createOrder({ takerFeeAssetData: BAD_ASSET_DATA }));
|
||||||
|
const signatures = orders.map(() => encodeExchangeBehavior());
|
||||||
|
const qfr = getExpectedSellQuoteFillResults(orders);
|
||||||
|
const tx = host
|
||||||
|
.executeTransform(
|
||||||
|
transformer.address,
|
||||||
|
takerToken.address,
|
||||||
|
qfr.takerAssetSpent,
|
||||||
|
encodeTransformData({
|
||||||
|
orders,
|
||||||
|
signatures,
|
||||||
|
buyAmount: qfr.makerAssetBought,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid });
|
||||||
|
return expect(tx).to.revertWith(
|
||||||
|
new ZeroExRevertErrors.TransformERC20.InvalidERC20AssetDataError(BAD_ASSET_DATA),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fails if an order has a fee asset that is neither maker or taker asset', async () => {
|
||||||
|
const badToken = randomAddress();
|
||||||
|
const BAD_ASSET_DATA = hexUtils.concat(ERC20_ASSET_PROXY_ID, hexUtils.leftPad(badToken));
|
||||||
|
const orders = _.times(1, () => createOrder({ takerFeeAssetData: BAD_ASSET_DATA }));
|
||||||
|
const signatures = orders.map(() => encodeExchangeBehavior());
|
||||||
|
const qfr = getExpectedSellQuoteFillResults(orders);
|
||||||
|
const tx = host
|
||||||
|
.executeTransform(
|
||||||
|
transformer.address,
|
||||||
|
takerToken.address,
|
||||||
|
qfr.takerAssetSpent,
|
||||||
|
encodeTransformData({
|
||||||
|
orders,
|
||||||
|
signatures,
|
||||||
|
buyAmount: qfr.makerAssetBought,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.awaitTransactionSuccessAsync({ value: qfr.protocolFeePaid });
|
||||||
|
return expect(tx).to.revertWith(new ZeroExRevertErrors.TransformERC20.InvalidTakerFeeTokenError(badToken));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('respects `maxOrderFillAmounts`', async () => {
|
||||||
|
const orders = _.times(2, () => createOrder());
|
||||||
|
const signatures = orders.map(() => encodeExchangeBehavior());
|
||||||
|
const qfr = getExpectedSellQuoteFillResults(orders.slice(1));
|
||||||
|
const protocolFee = singleProtocolFee.times(2);
|
||||||
|
await host
|
||||||
|
.executeTransform(
|
||||||
|
transformer.address,
|
||||||
|
takerToken.address,
|
||||||
|
qfr.takerAssetSpent,
|
||||||
|
encodeTransformData({
|
||||||
|
orders,
|
||||||
|
signatures,
|
||||||
|
buyAmount: qfr.makerAssetBought,
|
||||||
|
// Skip the first order.
|
||||||
|
maxOrderFillAmounts: [ZERO_AMOUNT],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.awaitTransactionSuccessAsync({ value: protocolFee });
|
||||||
|
assertBalances(await getBalancesAsync(host.address), {
|
||||||
|
...ZERO_BALANCES,
|
||||||
|
makerAssetBalance: qfr.makerAssetBought,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
147
contracts/zero-ex/test/transformers/pay_taker_transformer.ts
Normal file
147
contracts/zero-ex/test/transformers/pay_taker_transformer.ts
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
import { blockchainTests, constants, expect, getRandomInteger, randomAddress } from '@0x/contracts-test-utils';
|
||||||
|
import { BigNumber, hexUtils } from '@0x/utils';
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
|
import { ETH_TOKEN_ADDRESS } from '../../src/constants';
|
||||||
|
import { encodePayTakerTransformerData } from '../../src/transformer_data_encoders';
|
||||||
|
import { artifacts } from '../artifacts';
|
||||||
|
import { PayTakerTransformerContract, TestMintableERC20TokenContract, TestTransformerHostContract } from '../wrappers';
|
||||||
|
|
||||||
|
const { MAX_UINT256, ZERO_AMOUNT } = constants;
|
||||||
|
|
||||||
|
blockchainTests.resets('PayTakerTransformer', env => {
|
||||||
|
let caller: string;
|
||||||
|
const taker = randomAddress();
|
||||||
|
let token: TestMintableERC20TokenContract;
|
||||||
|
let transformer: PayTakerTransformerContract;
|
||||||
|
let host: TestTransformerHostContract;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
[caller] = await env.getAccountAddressesAsync();
|
||||||
|
token = await TestMintableERC20TokenContract.deployFrom0xArtifactAsync(
|
||||||
|
artifacts.TestMintableERC20Token,
|
||||||
|
env.provider,
|
||||||
|
env.txDefaults,
|
||||||
|
artifacts,
|
||||||
|
);
|
||||||
|
transformer = await PayTakerTransformerContract.deployFrom0xArtifactAsync(
|
||||||
|
artifacts.PayTakerTransformer,
|
||||||
|
env.provider,
|
||||||
|
env.txDefaults,
|
||||||
|
artifacts,
|
||||||
|
);
|
||||||
|
host = await TestTransformerHostContract.deployFrom0xArtifactAsync(
|
||||||
|
artifacts.TestTransformerHost,
|
||||||
|
env.provider,
|
||||||
|
{ ...env.txDefaults, from: caller },
|
||||||
|
artifacts,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
interface Balances {
|
||||||
|
ethBalance: BigNumber;
|
||||||
|
tokenBalance: BigNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ZERO_BALANCES = {
|
||||||
|
ethBalance: ZERO_AMOUNT,
|
||||||
|
tokenBalance: ZERO_AMOUNT,
|
||||||
|
};
|
||||||
|
|
||||||
|
async function getBalancesAsync(owner: string): Promise<Balances> {
|
||||||
|
return {
|
||||||
|
ethBalance: await env.web3Wrapper.getBalanceInWeiAsync(owner),
|
||||||
|
tokenBalance: await token.balanceOf(owner).callAsync(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function mintHostTokensAsync(amount: BigNumber): Promise<void> {
|
||||||
|
await token.mint(host.address, amount).awaitTransactionSuccessAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sendEtherAsync(to: string, amount: BigNumber): Promise<void> {
|
||||||
|
await env.web3Wrapper.awaitTransactionSuccessAsync(
|
||||||
|
await env.web3Wrapper.sendTransactionAsync({
|
||||||
|
...env.txDefaults,
|
||||||
|
to,
|
||||||
|
from: caller,
|
||||||
|
value: amount,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
it('can transfer a token and ETH', async () => {
|
||||||
|
const amounts = _.times(2, () => getRandomInteger(1, '1e18'));
|
||||||
|
const data = encodePayTakerTransformerData({
|
||||||
|
amounts,
|
||||||
|
tokens: [token.address, ETH_TOKEN_ADDRESS],
|
||||||
|
});
|
||||||
|
await mintHostTokensAsync(amounts[0]);
|
||||||
|
await sendEtherAsync(host.address, amounts[1]);
|
||||||
|
await host
|
||||||
|
.rawExecuteTransform(transformer.address, hexUtils.random(), taker, data)
|
||||||
|
.awaitTransactionSuccessAsync();
|
||||||
|
expect(await getBalancesAsync(host.address)).to.deep.eq(ZERO_BALANCES);
|
||||||
|
expect(await getBalancesAsync(taker)).to.deep.eq({
|
||||||
|
tokenBalance: amounts[0],
|
||||||
|
ethBalance: amounts[1],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can transfer all of a token and ETH', async () => {
|
||||||
|
const amounts = _.times(2, () => getRandomInteger(1, '1e18'));
|
||||||
|
const data = encodePayTakerTransformerData({
|
||||||
|
amounts: [MAX_UINT256, MAX_UINT256],
|
||||||
|
tokens: [token.address, ETH_TOKEN_ADDRESS],
|
||||||
|
});
|
||||||
|
await mintHostTokensAsync(amounts[0]);
|
||||||
|
await sendEtherAsync(host.address, amounts[1]);
|
||||||
|
await host
|
||||||
|
.rawExecuteTransform(transformer.address, hexUtils.random(), taker, data)
|
||||||
|
.awaitTransactionSuccessAsync();
|
||||||
|
expect(await getBalancesAsync(host.address)).to.deep.eq(ZERO_BALANCES);
|
||||||
|
expect(await getBalancesAsync(taker)).to.deep.eq({
|
||||||
|
tokenBalance: amounts[0],
|
||||||
|
ethBalance: amounts[1],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can transfer all of a token and ETH (empty amounts)', async () => {
|
||||||
|
const amounts = _.times(2, () => getRandomInteger(1, '1e18'));
|
||||||
|
const data = encodePayTakerTransformerData({
|
||||||
|
amounts: [],
|
||||||
|
tokens: [token.address, ETH_TOKEN_ADDRESS],
|
||||||
|
});
|
||||||
|
await mintHostTokensAsync(amounts[0]);
|
||||||
|
await sendEtherAsync(host.address, amounts[1]);
|
||||||
|
await host
|
||||||
|
.rawExecuteTransform(transformer.address, hexUtils.random(), taker, data)
|
||||||
|
.awaitTransactionSuccessAsync();
|
||||||
|
expect(await getBalancesAsync(host.address)).to.deep.eq(ZERO_BALANCES);
|
||||||
|
expect(await getBalancesAsync(taker)).to.deep.eq({
|
||||||
|
tokenBalance: amounts[0],
|
||||||
|
ethBalance: amounts[1],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can transfer less than the balance of a token and ETH', async () => {
|
||||||
|
const amounts = _.times(2, () => getRandomInteger(1, '1e18'));
|
||||||
|
const data = encodePayTakerTransformerData({
|
||||||
|
amounts: amounts.map(a => a.dividedToIntegerBy(2)),
|
||||||
|
tokens: [token.address, ETH_TOKEN_ADDRESS],
|
||||||
|
});
|
||||||
|
await mintHostTokensAsync(amounts[0]);
|
||||||
|
await sendEtherAsync(host.address, amounts[1]);
|
||||||
|
await host
|
||||||
|
.rawExecuteTransform(transformer.address, hexUtils.random(), taker, data)
|
||||||
|
.awaitTransactionSuccessAsync();
|
||||||
|
expect(await getBalancesAsync(host.address)).to.deep.eq({
|
||||||
|
tokenBalance: amounts[0].minus(amounts[0].dividedToIntegerBy(2)),
|
||||||
|
ethBalance: amounts[1].minus(amounts[1].dividedToIntegerBy(2)),
|
||||||
|
});
|
||||||
|
expect(await getBalancesAsync(taker)).to.deep.eq({
|
||||||
|
tokenBalance: amounts[0].dividedToIntegerBy(2),
|
||||||
|
ethBalance: amounts[1].dividedToIntegerBy(2),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
147
contracts/zero-ex/test/transformers/weth_transformer_test.ts
Normal file
147
contracts/zero-ex/test/transformers/weth_transformer_test.ts
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
import { blockchainTests, constants, expect, getRandomInteger, randomAddress } from '@0x/contracts-test-utils';
|
||||||
|
import { BigNumber, ZeroExRevertErrors } from '@0x/utils';
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
|
import { ETH_TOKEN_ADDRESS } from '../../src/constants';
|
||||||
|
import { encodeWethTransformerData } from '../../src/transformer_data_encoders';
|
||||||
|
import { artifacts } from '../artifacts';
|
||||||
|
import { TestWethContract, TestWethTransformerHostContract, WethTransformerContract } from '../wrappers';
|
||||||
|
|
||||||
|
const { MAX_UINT256, ZERO_AMOUNT } = constants;
|
||||||
|
|
||||||
|
blockchainTests.resets('WethTransformer', env => {
|
||||||
|
let weth: TestWethContract;
|
||||||
|
let transformer: WethTransformerContract;
|
||||||
|
let host: TestWethTransformerHostContract;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
weth = await TestWethContract.deployFrom0xArtifactAsync(
|
||||||
|
artifacts.TestWeth,
|
||||||
|
env.provider,
|
||||||
|
env.txDefaults,
|
||||||
|
artifacts,
|
||||||
|
);
|
||||||
|
transformer = await WethTransformerContract.deployFrom0xArtifactAsync(
|
||||||
|
artifacts.WethTransformer,
|
||||||
|
env.provider,
|
||||||
|
env.txDefaults,
|
||||||
|
artifacts,
|
||||||
|
weth.address,
|
||||||
|
);
|
||||||
|
host = await TestWethTransformerHostContract.deployFrom0xArtifactAsync(
|
||||||
|
artifacts.TestWethTransformerHost,
|
||||||
|
env.provider,
|
||||||
|
env.txDefaults,
|
||||||
|
artifacts,
|
||||||
|
weth.address,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
interface Balances {
|
||||||
|
ethBalance: BigNumber;
|
||||||
|
wethBalance: BigNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getHostBalancesAsync(): Promise<Balances> {
|
||||||
|
return {
|
||||||
|
ethBalance: await env.web3Wrapper.getBalanceInWeiAsync(host.address),
|
||||||
|
wethBalance: await weth.balanceOf(host.address).callAsync(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
it('fails if the token is neither ETH or WETH', async () => {
|
||||||
|
const amount = getRandomInteger(1, '1e18');
|
||||||
|
const data = encodeWethTransformerData({
|
||||||
|
amount,
|
||||||
|
token: randomAddress(),
|
||||||
|
});
|
||||||
|
const tx = host
|
||||||
|
.executeTransform(amount, transformer.address, data)
|
||||||
|
.awaitTransactionSuccessAsync({ value: amount });
|
||||||
|
return expect(tx).to.revertWith(new ZeroExRevertErrors.TransformERC20.InvalidTransformDataError(data));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can unwrap WETH', async () => {
|
||||||
|
const amount = getRandomInteger(1, '1e18');
|
||||||
|
const data = encodeWethTransformerData({
|
||||||
|
amount,
|
||||||
|
token: weth.address,
|
||||||
|
});
|
||||||
|
await host.executeTransform(amount, transformer.address, data).awaitTransactionSuccessAsync({ value: amount });
|
||||||
|
expect(await getHostBalancesAsync()).to.deep.eq({
|
||||||
|
ethBalance: amount,
|
||||||
|
wethBalance: ZERO_AMOUNT,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can unwrap all WETH', async () => {
|
||||||
|
const amount = getRandomInteger(1, '1e18');
|
||||||
|
const data = encodeWethTransformerData({
|
||||||
|
amount: MAX_UINT256,
|
||||||
|
token: weth.address,
|
||||||
|
});
|
||||||
|
await host.executeTransform(amount, transformer.address, data).awaitTransactionSuccessAsync({ value: amount });
|
||||||
|
expect(await getHostBalancesAsync()).to.deep.eq({
|
||||||
|
ethBalance: amount,
|
||||||
|
wethBalance: ZERO_AMOUNT,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can unwrap some WETH', async () => {
|
||||||
|
const amount = getRandomInteger(1, '1e18');
|
||||||
|
const data = encodeWethTransformerData({
|
||||||
|
amount: amount.dividedToIntegerBy(2),
|
||||||
|
token: weth.address,
|
||||||
|
});
|
||||||
|
await host.executeTransform(amount, transformer.address, data).awaitTransactionSuccessAsync({ value: amount });
|
||||||
|
expect(await getHostBalancesAsync()).to.deep.eq({
|
||||||
|
ethBalance: amount.dividedToIntegerBy(2),
|
||||||
|
wethBalance: amount.minus(amount.dividedToIntegerBy(2)),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can wrap ETH', async () => {
|
||||||
|
const amount = getRandomInteger(1, '1e18');
|
||||||
|
const data = encodeWethTransformerData({
|
||||||
|
amount,
|
||||||
|
token: ETH_TOKEN_ADDRESS,
|
||||||
|
});
|
||||||
|
await host
|
||||||
|
.executeTransform(ZERO_AMOUNT, transformer.address, data)
|
||||||
|
.awaitTransactionSuccessAsync({ value: amount });
|
||||||
|
expect(await getHostBalancesAsync()).to.deep.eq({
|
||||||
|
ethBalance: ZERO_AMOUNT,
|
||||||
|
wethBalance: amount,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can wrap all ETH', async () => {
|
||||||
|
const amount = getRandomInteger(1, '1e18');
|
||||||
|
const data = encodeWethTransformerData({
|
||||||
|
amount: MAX_UINT256,
|
||||||
|
token: ETH_TOKEN_ADDRESS,
|
||||||
|
});
|
||||||
|
await host
|
||||||
|
.executeTransform(ZERO_AMOUNT, transformer.address, data)
|
||||||
|
.awaitTransactionSuccessAsync({ value: amount });
|
||||||
|
expect(await getHostBalancesAsync()).to.deep.eq({
|
||||||
|
ethBalance: ZERO_AMOUNT,
|
||||||
|
wethBalance: amount,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can wrap some ETH', async () => {
|
||||||
|
const amount = getRandomInteger(1, '1e18');
|
||||||
|
const data = encodeWethTransformerData({
|
||||||
|
amount: amount.dividedToIntegerBy(2),
|
||||||
|
token: ETH_TOKEN_ADDRESS,
|
||||||
|
});
|
||||||
|
await host
|
||||||
|
.executeTransform(ZERO_AMOUNT, transformer.address, data)
|
||||||
|
.awaitTransactionSuccessAsync({ value: amount });
|
||||||
|
expect(await getHostBalancesAsync()).to.deep.eq({
|
||||||
|
ethBalance: amount.minus(amount.dividedToIntegerBy(2)),
|
||||||
|
wethBalance: amount.dividedToIntegerBy(2),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -5,12 +5,14 @@
|
|||||||
*/
|
*/
|
||||||
export * from '../test/generated-wrappers/allowance_target';
|
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/fixin_common';
|
export * from '../test/generated-wrappers/fixin_common';
|
||||||
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_transformer';
|
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_feature';
|
||||||
export * from '../test/generated-wrappers/i_flash_wallet';
|
export * from '../test/generated-wrappers/i_flash_wallet';
|
||||||
export * from '../test/generated-wrappers/i_ownable';
|
export * from '../test/generated-wrappers/i_ownable';
|
||||||
@@ -49,8 +51,12 @@ export * from '../test/generated-wrappers/test_simple_function_registry_feature_
|
|||||||
export * from '../test/generated-wrappers/test_token_spender';
|
export * from '../test/generated-wrappers/test_token_spender';
|
||||||
export * from '../test/generated-wrappers/test_token_spender_erc20_token';
|
export * from '../test/generated-wrappers/test_token_spender_erc20_token';
|
||||||
export * from '../test/generated-wrappers/test_transform_erc20';
|
export * from '../test/generated-wrappers/test_transform_erc20';
|
||||||
|
export * from '../test/generated-wrappers/test_transformer_host';
|
||||||
|
export * from '../test/generated-wrappers/test_weth';
|
||||||
|
export * from '../test/generated-wrappers/test_weth_transformer_host';
|
||||||
export * from '../test/generated-wrappers/test_zero_ex_feature';
|
export * from '../test/generated-wrappers/test_zero_ex_feature';
|
||||||
export * from '../test/generated-wrappers/token_spender';
|
export * from '../test/generated-wrappers/token_spender';
|
||||||
export * from '../test/generated-wrappers/token_spender_puppet';
|
export * from '../test/generated-wrappers/token_spender_puppet';
|
||||||
export * from '../test/generated-wrappers/transform_erc20';
|
export * from '../test/generated-wrappers/transform_erc20';
|
||||||
|
export * from '../test/generated-wrappers/weth_transformer';
|
||||||
export * from '../test/generated-wrappers/zero_ex';
|
export * from '../test/generated-wrappers/zero_ex';
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
"compilerOptions": { "outDir": "lib", "rootDir": ".", "resolveJsonModule": true },
|
"compilerOptions": { "outDir": "lib", "rootDir": ".", "resolveJsonModule": true },
|
||||||
"include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*"],
|
"include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*"],
|
||||||
"files": [
|
"files": [
|
||||||
|
"generated-artifacts/FillQuoteTransformer.json",
|
||||||
"generated-artifacts/FullMigration.json",
|
"generated-artifacts/FullMigration.json",
|
||||||
"generated-artifacts/IAllowanceTarget.json",
|
"generated-artifacts/IAllowanceTarget.json",
|
||||||
"generated-artifacts/IERC20Transformer.json",
|
"generated-artifacts/IERC20Transformer.json",
|
||||||
@@ -15,12 +16,14 @@
|
|||||||
"generated-artifacts/ZeroEx.json",
|
"generated-artifacts/ZeroEx.json",
|
||||||
"test/generated-artifacts/AllowanceTarget.json",
|
"test/generated-artifacts/AllowanceTarget.json",
|
||||||
"test/generated-artifacts/Bootstrap.json",
|
"test/generated-artifacts/Bootstrap.json",
|
||||||
|
"test/generated-artifacts/FillQuoteTransformer.json",
|
||||||
"test/generated-artifacts/FixinCommon.json",
|
"test/generated-artifacts/FixinCommon.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/IERC20Transformer.json",
|
"test/generated-artifacts/IERC20Transformer.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/IOwnable.json",
|
"test/generated-artifacts/IOwnable.json",
|
||||||
@@ -59,10 +62,14 @@
|
|||||||
"test/generated-artifacts/TestTokenSpender.json",
|
"test/generated-artifacts/TestTokenSpender.json",
|
||||||
"test/generated-artifacts/TestTokenSpenderERC20Token.json",
|
"test/generated-artifacts/TestTokenSpenderERC20Token.json",
|
||||||
"test/generated-artifacts/TestTransformERC20.json",
|
"test/generated-artifacts/TestTransformERC20.json",
|
||||||
|
"test/generated-artifacts/TestTransformerHost.json",
|
||||||
|
"test/generated-artifacts/TestWeth.json",
|
||||||
|
"test/generated-artifacts/TestWethTransformerHost.json",
|
||||||
"test/generated-artifacts/TestZeroExFeature.json",
|
"test/generated-artifacts/TestZeroExFeature.json",
|
||||||
"test/generated-artifacts/TokenSpender.json",
|
"test/generated-artifacts/TokenSpender.json",
|
||||||
"test/generated-artifacts/TokenSpenderPuppet.json",
|
"test/generated-artifacts/TokenSpenderPuppet.json",
|
||||||
"test/generated-artifacts/TransformERC20.json",
|
"test/generated-artifacts/TransformERC20.json",
|
||||||
|
"test/generated-artifacts/WethTransformer.json",
|
||||||
"test/generated-artifacts/ZeroEx.json"
|
"test/generated-artifacts/ZeroEx.json"
|
||||||
],
|
],
|
||||||
"exclude": ["./deploy/solc/solc_bin"]
|
"exclude": ["./deploy/solc/solc_bin"]
|
||||||
|
|||||||
Reference in New Issue
Block a user