@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 /////////////////////////////////////////////
|
||||
|
||||
function IncompleteFillSellQuoteError(
|
||||
@@ -177,24 +192,7 @@ library LibTransformERC20RichErrors {
|
||||
);
|
||||
}
|
||||
|
||||
// WethTransformer errors ////////////////////////////////////////////////////
|
||||
|
||||
function WrongNumberOfTokensReceivedError(
|
||||
uint256 actual,
|
||||
uint256 expected
|
||||
)
|
||||
internal
|
||||
pure
|
||||
returns (bytes memory)
|
||||
{
|
||||
return abi.encodeWithSelector(
|
||||
bytes4(keccak256("WrongNumberOfTokensReceivedError(uint256,uint256)")),
|
||||
actual,
|
||||
expected
|
||||
);
|
||||
}
|
||||
|
||||
function InvalidTokenReceivedError(
|
||||
function InvalidTakerFeeTokenError(
|
||||
address token
|
||||
)
|
||||
internal
|
||||
@@ -202,8 +200,9 @@ library LibTransformERC20RichErrors {
|
||||
returns (bytes memory)
|
||||
{
|
||||
return abi.encodeWithSelector(
|
||||
bytes4(keccak256("InvalidTokenReceivedError(address)")),
|
||||
bytes4(keccak256("InvalidTakerFeeTokenError(address)")),
|
||||
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;
|
||||
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"
|
||||
},
|
||||
"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": "./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-test-utils": "^5.3.2",
|
||||
"@0x/dev-utils": "^3.2.1",
|
||||
"@0x/order-utils": "^10.2.4",
|
||||
"@0x/sol-compiler": "^4.0.8",
|
||||
"@0x/subproviders": "^6.0.8",
|
||||
"@0x/ts-doc-gen": "^0.0.22",
|
||||
|
@@ -5,25 +5,31 @@
|
||||
*/
|
||||
import { ContractArtifact } from 'ethereum-types';
|
||||
|
||||
import * as FillQuoteTransformer from '../generated-artifacts/FillQuoteTransformer.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 IFlashWallet from '../generated-artifacts/IFlashWallet.json';
|
||||
import * as InitialMigration from '../generated-artifacts/InitialMigration.json';
|
||||
import * as IOwnable from '../generated-artifacts/IOwnable.json';
|
||||
import * as ISimpleFunctionRegistry from '../generated-artifacts/ISimpleFunctionRegistry.json';
|
||||
import * as ITokenSpender from '../generated-artifacts/ITokenSpender.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';
|
||||
export const artifacts = {
|
||||
ZeroEx: ZeroEx as ContractArtifact,
|
||||
FullMigration: FullMigration as ContractArtifact,
|
||||
InitialMigration: InitialMigration as ContractArtifact,
|
||||
IFlashWallet: IFlashWallet as ContractArtifact,
|
||||
IAllowanceTarget: IAllowanceTarget as ContractArtifact,
|
||||
Puppet: Puppet as ContractArtifact,
|
||||
IERC20Transformer: IERC20Transformer as ContractArtifact,
|
||||
IOwnable: IOwnable as ContractArtifact,
|
||||
ISimpleFunctionRegistry: ISimpleFunctionRegistry as ContractArtifact,
|
||||
ITokenSpender: ITokenSpender 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 {
|
||||
FillQuoteTransformerContract,
|
||||
IOwnableContract,
|
||||
IOwnableEvents,
|
||||
ISimpleFunctionRegistryContract,
|
||||
ISimpleFunctionRegistryEvents,
|
||||
ITokenSpenderContract,
|
||||
ITransformERC20Contract,
|
||||
PayTakerTransformerContract,
|
||||
WethTransformerContract,
|
||||
ZeroExContract,
|
||||
} from './wrappers';
|
||||
export { ZeroExRevertErrors } from '@0x/utils';
|
||||
@@ -36,4 +41,6 @@ export {
|
||||
TupleDataItem,
|
||||
StateMutability,
|
||||
} 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.
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
export * from '../generated-wrappers/fill_quote_transformer';
|
||||
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_flash_wallet';
|
||||
export * from '../generated-wrappers/i_ownable';
|
||||
export * from '../generated-wrappers/i_simple_function_registry';
|
||||
export * from '../generated-wrappers/i_token_spender';
|
||||
export * from '../generated-wrappers/i_transform_erc20';
|
||||
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';
|
||||
|
@@ -7,16 +7,17 @@ import { ContractArtifact } from 'ethereum-types';
|
||||
|
||||
import * as AllowanceTarget from '../test/generated-artifacts/AllowanceTarget.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 FlashWallet from '../test/generated-artifacts/FlashWallet.json';
|
||||
import * as FullMigration from '../test/generated-artifacts/FullMigration.json';
|
||||
import * as IAllowanceTarget from '../test/generated-artifacts/IAllowanceTarget.json';
|
||||
import * as IBootstrap from '../test/generated-artifacts/IBootstrap.json';
|
||||
import * as IERC20Transformer from '../test/generated-artifacts/IERC20Transformer.json';
|
||||
import * as IExchange from '../test/generated-artifacts/IExchange.json';
|
||||
import * as IFeature from '../test/generated-artifacts/IFeature.json';
|
||||
import * as IFlashWallet from '../test/generated-artifacts/IFlashWallet.json';
|
||||
import * as InitialMigration from '../test/generated-artifacts/InitialMigration.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 ITestSimpleFunctionRegistryFeature from '../test/generated-artifacts/ITestSimpleFunctionRegistryFeature.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 LibProxyRichErrors from '../test/generated-artifacts/LibProxyRichErrors.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 LibSimpleFunctionRegistryStorage from '../test/generated-artifacts/LibSimpleFunctionRegistryStorage.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 LibTransformERC20RichErrors from '../test/generated-artifacts/LibTransformERC20RichErrors.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 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 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 TestInitialMigration from '../test/generated-artifacts/TestInitialMigration.json';
|
||||
import * as TestMigrator from '../test/generated-artifacts/TestMigrator.json';
|
||||
import * as TestMintableERC20Token from '../test/generated-artifacts/TestMintableERC20Token.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 TestSimpleFunctionRegistryFeatureImpl2 from '../test/generated-artifacts/TestSimpleFunctionRegistryFeatureImpl2.json';
|
||||
import * as TestTokenSpender from '../test/generated-artifacts/TestTokenSpender.json';
|
||||
import * as TestTokenSpenderERC20Token from '../test/generated-artifacts/TestTokenSpenderERC20Token.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 TokenSpender from '../test/generated-artifacts/TokenSpender.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';
|
||||
export const artifacts = {
|
||||
ZeroEx: ZeroEx as ContractArtifact,
|
||||
LibCommonRichErrors: LibCommonRichErrors as ContractArtifact,
|
||||
LibOwnableRichErrors: LibOwnableRichErrors as ContractArtifact,
|
||||
LibProxyRichErrors: LibProxyRichErrors as ContractArtifact,
|
||||
LibPuppetRichErrors: LibPuppetRichErrors as ContractArtifact,
|
||||
LibSimpleFunctionRegistryRichErrors: LibSimpleFunctionRegistryRichErrors as ContractArtifact,
|
||||
LibSpenderRichErrors: LibSpenderRichErrors as ContractArtifact,
|
||||
LibTransformERC20RichErrors: LibTransformERC20RichErrors as ContractArtifact,
|
||||
LibWalletRichErrors: LibWalletRichErrors as ContractArtifact,
|
||||
AllowanceTarget: AllowanceTarget as ContractArtifact,
|
||||
FlashWallet: FlashWallet as ContractArtifact,
|
||||
IAllowanceTarget: IAllowanceTarget as ContractArtifact,
|
||||
IFlashWallet: IFlashWallet as ContractArtifact,
|
||||
IPuppet: IPuppet as ContractArtifact,
|
||||
Puppet: Puppet as ContractArtifact,
|
||||
Bootstrap: Bootstrap as ContractArtifact,
|
||||
IBootstrap: IBootstrap as ContractArtifact,
|
||||
IFeature: IFeature as ContractArtifact,
|
||||
@@ -89,19 +98,28 @@ export const artifacts = {
|
||||
LibStorage: LibStorage as ContractArtifact,
|
||||
LibTokenSpenderStorage: LibTokenSpenderStorage as ContractArtifact,
|
||||
LibTransformERC20Storage: LibTransformERC20Storage as ContractArtifact,
|
||||
FillQuoteTransformer: FillQuoteTransformer as ContractArtifact,
|
||||
IERC20Transformer: IERC20Transformer as ContractArtifact,
|
||||
LibERC20Transformer: LibERC20Transformer as ContractArtifact,
|
||||
PayTakerTransformer: PayTakerTransformer as ContractArtifact,
|
||||
WethTransformer: WethTransformer as ContractArtifact,
|
||||
IExchange: IExchange as ContractArtifact,
|
||||
ITestSimpleFunctionRegistryFeature: ITestSimpleFunctionRegistryFeature as ContractArtifact,
|
||||
TestCallTarget: TestCallTarget as ContractArtifact,
|
||||
TestFillQuoteTransformerExchange: TestFillQuoteTransformerExchange as ContractArtifact,
|
||||
TestFillQuoteTransformerHost: TestFillQuoteTransformerHost as ContractArtifact,
|
||||
TestFullMigration: TestFullMigration as ContractArtifact,
|
||||
TestInitialMigration: TestInitialMigration as ContractArtifact,
|
||||
TestMigrator: TestMigrator as ContractArtifact,
|
||||
TestMintTokenERC20Transformer: TestMintTokenERC20Transformer as ContractArtifact,
|
||||
TestMintableERC20Token: TestMintableERC20Token as ContractArtifact,
|
||||
TestPuppetTarget: TestPuppetTarget as ContractArtifact,
|
||||
TestSimpleFunctionRegistryFeatureImpl1: TestSimpleFunctionRegistryFeatureImpl1 as ContractArtifact,
|
||||
TestSimpleFunctionRegistryFeatureImpl2: TestSimpleFunctionRegistryFeatureImpl2 as ContractArtifact,
|
||||
TestTokenSpender: TestTokenSpender as ContractArtifact,
|
||||
TestTokenSpenderERC20Token: TestTokenSpenderERC20Token as ContractArtifact,
|
||||
TestTransformERC20: TestTransformERC20 as ContractArtifact,
|
||||
TestTransformerHost: TestTransformerHost as ContractArtifact,
|
||||
TestWeth: TestWeth as ContractArtifact,
|
||||
TestWethTransformerHost: TestWethTransformerHost as ContractArtifact,
|
||||
TestZeroExFeature: TestZeroExFeature as ContractArtifact,
|
||||
};
|
||||
|
@@ -10,6 +10,7 @@ import {
|
||||
} from '@0x/contracts-test-utils';
|
||||
import { AbiEncoder, hexUtils, ZeroExRevertErrors } from '@0x/utils';
|
||||
|
||||
import { ETH_TOKEN_ADDRESS } from '../../src/constants';
|
||||
import { getRLPEncodedAccountNonceAsync } from '../../src/nonce_utils';
|
||||
import { artifacts } from '../artifacts';
|
||||
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 () => {
|
||||
const startingInputTokenBalance = getRandomInteger(0, '100e18');
|
||||
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/bootstrap';
|
||||
export * from '../test/generated-wrappers/fill_quote_transformer';
|
||||
export * from '../test/generated-wrappers/fixin_common';
|
||||
export * from '../test/generated-wrappers/flash_wallet';
|
||||
export * from '../test/generated-wrappers/full_migration';
|
||||
export * from '../test/generated-wrappers/i_allowance_target';
|
||||
export * from '../test/generated-wrappers/i_bootstrap';
|
||||
export * from '../test/generated-wrappers/i_erc20_transformer';
|
||||
export * from '../test/generated-wrappers/i_exchange';
|
||||
export * from '../test/generated-wrappers/i_feature';
|
||||
export * from '../test/generated-wrappers/i_flash_wallet';
|
||||
export * from '../test/generated-wrappers/i_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_erc20_token';
|
||||
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/token_spender';
|
||||
export * from '../test/generated-wrappers/token_spender_puppet';
|
||||
export * from '../test/generated-wrappers/transform_erc20';
|
||||
export * from '../test/generated-wrappers/weth_transformer';
|
||||
export * from '../test/generated-wrappers/zero_ex';
|
||||
|
@@ -3,6 +3,7 @@
|
||||
"compilerOptions": { "outDir": "lib", "rootDir": ".", "resolveJsonModule": true },
|
||||
"include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*"],
|
||||
"files": [
|
||||
"generated-artifacts/FillQuoteTransformer.json",
|
||||
"generated-artifacts/FullMigration.json",
|
||||
"generated-artifacts/IAllowanceTarget.json",
|
||||
"generated-artifacts/IERC20Transformer.json",
|
||||
@@ -15,12 +16,14 @@
|
||||
"generated-artifacts/ZeroEx.json",
|
||||
"test/generated-artifacts/AllowanceTarget.json",
|
||||
"test/generated-artifacts/Bootstrap.json",
|
||||
"test/generated-artifacts/FillQuoteTransformer.json",
|
||||
"test/generated-artifacts/FixinCommon.json",
|
||||
"test/generated-artifacts/FlashWallet.json",
|
||||
"test/generated-artifacts/FullMigration.json",
|
||||
"test/generated-artifacts/IAllowanceTarget.json",
|
||||
"test/generated-artifacts/IBootstrap.json",
|
||||
"test/generated-artifacts/IERC20Transformer.json",
|
||||
"test/generated-artifacts/IExchange.json",
|
||||
"test/generated-artifacts/IFeature.json",
|
||||
"test/generated-artifacts/IFlashWallet.json",
|
||||
"test/generated-artifacts/IOwnable.json",
|
||||
@@ -59,10 +62,14 @@
|
||||
"test/generated-artifacts/TestTokenSpender.json",
|
||||
"test/generated-artifacts/TestTokenSpenderERC20Token.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/TokenSpender.json",
|
||||
"test/generated-artifacts/TokenSpenderPuppet.json",
|
||||
"test/generated-artifacts/TransformERC20.json",
|
||||
"test/generated-artifacts/WethTransformer.json",
|
||||
"test/generated-artifacts/ZeroEx.json"
|
||||
],
|
||||
"exclude": ["./deploy/solc/solc_bin"]
|
||||
|
Reference in New Issue
Block a user