update contracts and tests to support different maker assets

This commit is contained in:
Michael Zhu 2019-10-01 14:15:03 -07:00
parent 0ff8b12770
commit 8077123e9f
4 changed files with 143 additions and 95 deletions

View File

@ -27,10 +27,12 @@ import "@0x/contracts-exchange-libs/contracts/src/LibMath.sol";
import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol";
import "./libs/LibConstants.sol";
import "./libs/LibForwarderRichErrors.sol";
import "./MixinAssets.sol";
contract MixinExchangeWrapper is
LibConstants
LibConstants,
MixinAssets
{
using LibSafeMath for uint256;
@ -72,14 +74,12 @@ contract MixinExchangeWrapper is
/// @param order A single order specification.
/// @param signature Signature for the given order.
/// @param remainingTakerAssetFillAmount Remaining amount of WETH to sell.
/// @param protocolFee Amount of WETH that will be spent on the protocol fee for each order.
/// @return wethSpentAmount Amount of WETH spent on the given order.
/// @return makerAssetAcquiredAmount Amount of maker asset acquired from the given order.
function _marketSellSingleOrder(
LibOrder.Order memory order,
bytes memory signature,
uint256 remainingTakerAssetFillAmount,
uint256 protocolFee
uint256 remainingTakerAssetFillAmount
)
internal
returns (
@ -92,7 +92,7 @@ contract MixinExchangeWrapper is
// Attempt to sell the remaining amount of WETH
LibFillResults.FillResults memory singleFillResults = _fillOrderNoThrow(
order,
remainingTakerAssetFillAmount.safeSub(protocolFee),
remainingTakerAssetFillAmount,
signature
);
@ -110,7 +110,7 @@ contract MixinExchangeWrapper is
uint256 takerAssetFillAmount = LibMath.getPartialAmountCeil(
order.takerAssetAmount,
order.takerAssetAmount.safeAdd(order.takerFee),
remainingTakerAssetFillAmount.safeSub(protocolFee)
remainingTakerAssetFillAmount
);
LibFillResults.FillResults memory singleFillResults = _fillOrderNoThrow(
@ -161,7 +161,8 @@ contract MixinExchangeWrapper is
// The remaining amount of WETH to sell
uint256 remainingTakerAssetFillAmount = wethSellAmount
.safeSub(totalWethSpentAmount);
.safeSub(totalWethSpentAmount)
.safeSub(protocolFee);
(
uint256 wethSpentAmount,
@ -169,10 +170,11 @@ contract MixinExchangeWrapper is
) = _marketSellSingleOrder(
orders[i],
signatures[i],
remainingTakerAssetFillAmount,
protocolFee
remainingTakerAssetFillAmount
);
_transferAssetToSender(orders[i].makerAssetData, makerAssetAcquiredAmount);
totalWethSpentAmount = totalWethSpentAmount
.safeAdd(wethSpentAmount);
totalMakerAssetAcquiredAmount = totalMakerAssetAcquiredAmount
@ -294,6 +296,8 @@ contract MixinExchangeWrapper is
remainingMakerAssetFillAmount
);
_transferAssetToSender(orders[i].makerAssetData, makerAssetAcquiredAmount);
totalWethSpentAmount = totalWethSpentAmount
.safeAdd(wethSpentAmount);
totalMakerAssetAcquiredAmount = totalMakerAssetAcquiredAmount

View File

@ -28,7 +28,6 @@ import "./libs/LibConstants.sol";
import "./libs/LibForwarderRichErrors.sol";
import "./interfaces/IAssets.sol";
import "./interfaces/IForwarderCore.sol";
import "./MixinAssets.sol";
import "./MixinExchangeWrapper.sol";
import "./MixinWeth.sol";
@ -38,7 +37,6 @@ contract MixinForwarderCore is
IAssets,
IForwarderCore,
MixinWeth,
MixinAssets,
MixinExchangeWrapper
{
using LibBytes for bytes;
@ -93,7 +91,8 @@ contract MixinForwarderCore is
msg.value
);
// Spends up to wethSellAmount to fill orders and pay WETH order fees.
// Spends up to wethSellAmount to fill orders, transfers purchased assets to msg.sender,
// and pays WETH order fees.
(
wethSpentAmount,
makerAssetAcquiredAmount
@ -110,12 +109,6 @@ contract MixinForwarderCore is
feePercentage,
feeRecipient
);
// Transfer purchased assets to msg.sender.
_transferAssetToSender(
orders[0].makerAssetData,
makerAssetAcquiredAmount
);
}
/// @dev Attempt to buy makerAssetBuyAmount of makerAsset by selling ETH provided with transaction.
@ -148,9 +141,7 @@ contract MixinForwarderCore is
// Convert ETH to WETH.
_convertEthToWeth();
// Attempt to fill the desired amount of makerAsset. Note that makerAssetAcquiredAmount < makerAssetBuyAmount
// if any of the orders filled have an takerFee denominated in makerAsset, since these fees will be paid out
// from the Forwarder's temporary makerAsset balance.
// Attempts to fill the desired amount of makerAsset and trasnfer purchased assets to msg.sender.
(
wethSpentAmount,
makerAssetAcquiredAmount
@ -167,11 +158,5 @@ contract MixinForwarderCore is
feePercentage,
feeRecipient
);
// Transfer acquired assets to msg.sender.
_transferAssetToSender(
orders[0].makerAssetData,
makerAssetAcquiredAmount
);
}
}

View File

@ -154,7 +154,7 @@ blockchainTests(ContractName.Forwarder, env => {
wethAssetData,
);
forwarderWrapper = new ForwarderWrapper(forwarderContract, env.provider);
await forwarderWrapper.approveMakerAssetProxyAsync(assetDataUtils.encodeERC20AssetData(erc20Token.address), {
await forwarderWrapper.approveMakerAssetProxyAsync(defaultOrderParams.makerAssetData, {
from: takerAddress,
});
erc20Wrapper.addTokenOwnerAddress(forwarderContract.address);
@ -203,7 +203,7 @@ blockchainTests(ContractName.Forwarder, env => {
blockchainTests.resets('marketSellOrdersWithEth without extra fees', () => {
it('should fill a single order without a taker fee', async () => {
const orderWithoutFee = await orderFactory.newSignedOrderAsync();
await forwarderTestFactory.marketSellTestAsync([orderWithoutFee], 0.78, erc20Token);
await forwarderTestFactory.marketSellTestAsync([orderWithoutFee], 0.78, [erc20Token]);
});
it('should fill multiple orders without taker fees', async () => {
const firstOrder = await orderFactory.newSignedOrderAsync();
@ -212,14 +212,14 @@ blockchainTests(ContractName.Forwarder, env => {
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(21, DECIMALS_DEFAULT),
});
const orders = [firstOrder, secondOrder];
await forwarderTestFactory.marketSellTestAsync(orders, 1.51, erc20Token);
await forwarderTestFactory.marketSellTestAsync(orders, 1.51, [erc20Token]);
});
it('should fill a single order with a percentage fee', async () => {
const orderWithPercentageFee = await orderFactory.newSignedOrderAsync({
takerFee: Web3Wrapper.toBaseUnitAmount(1, DECIMALS_DEFAULT),
takerFeeAssetData: assetDataUtils.encodeERC20AssetData(defaultMakerAssetAddress),
});
await forwarderTestFactory.marketSellTestAsync([orderWithPercentageFee], 0.58, erc20Token);
await forwarderTestFactory.marketSellTestAsync([orderWithPercentageFee], 0.58, [erc20Token]);
});
it('should fill multiple orders with percentage fees', async () => {
const firstOrder = await orderFactory.newSignedOrderAsync({
@ -233,7 +233,7 @@ blockchainTests(ContractName.Forwarder, env => {
takerFeeAssetData: assetDataUtils.encodeERC20AssetData(defaultMakerAssetAddress),
});
const orders = [firstOrder, secondOrder];
await forwarderTestFactory.marketSellTestAsync(orders, 1.34, erc20Token);
await forwarderTestFactory.marketSellTestAsync(orders, 1.34, [erc20Token]);
});
it('should fail to fill an order with a percentage fee if the asset proxy is not yet approved', async () => {
const unapprovedAsset = assetDataUtils.encodeERC20AssetData(secondErc20Token.address);
@ -280,7 +280,7 @@ blockchainTests(ContractName.Forwarder, env => {
takerFee: Web3Wrapper.toBaseUnitAmount(1, DECIMALS_DEFAULT),
takerFeeAssetData: wethAssetData,
});
await forwarderTestFactory.marketSellTestAsync([orderWithWethFee], 0.13, erc20Token);
await forwarderTestFactory.marketSellTestAsync([orderWithWethFee], 0.13, [erc20Token]);
});
it('should fill multiple orders with WETH fees', async () => {
const firstOrder = await orderFactory.newSignedOrderAsync({
@ -294,7 +294,7 @@ blockchainTests(ContractName.Forwarder, env => {
takerFeeAssetData: wethAssetData,
});
const orders = [firstOrder, secondOrderWithWethFee];
await forwarderTestFactory.marketSellTestAsync(orders, 1.25, erc20Token);
await forwarderTestFactory.marketSellTestAsync(orders, 1.25, [erc20Token]);
});
it('should refund remaining ETH if amount is greater than takerAssetAmount', async () => {
const order = await orderFactory.newSignedOrderAsync();
@ -310,6 +310,21 @@ blockchainTests(ContractName.Forwarder, env => {
expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
});
it('should fill orders with different makerAssetData', async () => {
const firstOrderMakerAssetData = assetDataUtils.encodeERC20AssetData(erc20Token.address);
const firstOrder = await orderFactory.newSignedOrderAsync({
makerAssetData: firstOrderMakerAssetData,
});
const secondOrderMakerAssetData = assetDataUtils.encodeERC20AssetData(secondErc20Token.address);
const secondOrder = await orderFactory.newSignedOrderAsync({
makerAssetData: secondOrderMakerAssetData,
});
await forwarderWrapper.approveMakerAssetProxyAsync(secondOrderMakerAssetData, { from: takerAddress });
const orders = [firstOrder, secondOrder];
await forwarderTestFactory.marketSellTestAsync(orders, 1.5, [erc20Token, secondErc20Token]);
});
it('should fail to fill an order with a fee denominated in an asset other than makerAsset or WETH', async () => {
const makerAssetData = assetDataUtils.encodeERC20AssetData(erc20Token.address);
const takerFeeAssetData = assetDataUtils.encodeERC20AssetData(secondErc20Token.address);
@ -321,14 +336,14 @@ blockchainTests(ContractName.Forwarder, env => {
});
const revertError = new ForwarderRevertErrors.UnsupportedFeeError(takerFeeAssetData);
await forwarderTestFactory.marketSellTestAsync([order], 0.5, erc20Token, {
await forwarderTestFactory.marketSellTestAsync([order], 0.5, [erc20Token], {
revertError,
});
});
it('should fill a partially-filled order without a taker fee', async () => {
const order = await orderFactory.newSignedOrderAsync();
await forwarderTestFactory.marketSellTestAsync([order], 0.3, erc20Token);
await forwarderTestFactory.marketSellTestAsync([order], 0.8, erc20Token);
await forwarderTestFactory.marketSellTestAsync([order], 0.3, [erc20Token]);
await forwarderTestFactory.marketSellTestAsync([order], 0.8, [erc20Token]);
});
it('should skip over an order with an invalid maker asset amount', async () => {
const unfillableOrder = await orderFactory.newSignedOrderAsync({
@ -336,7 +351,7 @@ blockchainTests(ContractName.Forwarder, env => {
});
const fillableOrder = await orderFactory.newSignedOrderAsync();
await forwarderTestFactory.marketSellTestAsync([unfillableOrder, fillableOrder], 1.5, erc20Token);
await forwarderTestFactory.marketSellTestAsync([unfillableOrder, fillableOrder], 1.5, [erc20Token]);
});
it('should skip over an order with an invalid taker asset amount', async () => {
const unfillableOrder = await orderFactory.newSignedOrderAsync({
@ -344,7 +359,7 @@ blockchainTests(ContractName.Forwarder, env => {
});
const fillableOrder = await orderFactory.newSignedOrderAsync();
await forwarderTestFactory.marketSellTestAsync([unfillableOrder, fillableOrder], 1.5, erc20Token);
await forwarderTestFactory.marketSellTestAsync([unfillableOrder, fillableOrder], 1.5, [erc20Token]);
});
it('should skip over an expired order', async () => {
const currentTimestamp = await getLatestBlockTimestampAsync();
@ -353,21 +368,21 @@ blockchainTests(ContractName.Forwarder, env => {
});
const fillableOrder = await orderFactory.newSignedOrderAsync();
await forwarderTestFactory.marketSellTestAsync([expiredOrder, fillableOrder], 1.5, erc20Token);
await forwarderTestFactory.marketSellTestAsync([expiredOrder, fillableOrder], 1.5, [erc20Token]);
});
it('should skip over a fully filled order', async () => {
const fullyFilledOrder = await orderFactory.newSignedOrderAsync();
await forwarderTestFactory.marketSellTestAsync([fullyFilledOrder], 1, erc20Token);
await forwarderTestFactory.marketSellTestAsync([fullyFilledOrder], 1, [erc20Token]);
const fillableOrder = await orderFactory.newSignedOrderAsync();
await forwarderTestFactory.marketSellTestAsync([fullyFilledOrder, fillableOrder], 1.5, erc20Token);
await forwarderTestFactory.marketSellTestAsync([fullyFilledOrder, fillableOrder], 1.5, [erc20Token]);
});
it('should skip over a cancelled order', async () => {
const cancelledOrder = await orderFactory.newSignedOrderAsync();
await exchangeWrapper.cancelOrderAsync(cancelledOrder, makerAddress);
const fillableOrder = await orderFactory.newSignedOrderAsync();
await forwarderTestFactory.marketSellTestAsync([cancelledOrder, fillableOrder], 1.5, erc20Token);
await forwarderTestFactory.marketSellTestAsync([cancelledOrder, fillableOrder], 1.5, [erc20Token]);
});
});
blockchainTests.resets('marketSellOrdersWithEth with extra fees', () => {
@ -376,7 +391,7 @@ blockchainTests(ContractName.Forwarder, env => {
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(157, DECIMALS_DEFAULT),
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(36, DECIMALS_DEFAULT),
});
await forwarderTestFactory.marketSellTestAsync([order], 0.67, erc20Token, {
await forwarderTestFactory.marketSellTestAsync([order], 0.67, [erc20Token], {
forwarderFeePercentage: new BigNumber(2),
});
});
@ -387,7 +402,7 @@ blockchainTests(ContractName.Forwarder, env => {
ForwarderTestFactory.getPercentageOfValue(constants.PERCENTAGE_DENOMINATOR, forwarderFeePercentage),
);
await forwarderTestFactory.marketSellTestAsync([order], 0.5, erc20Token, {
await forwarderTestFactory.marketSellTestAsync([order], 0.5, [erc20Token], {
forwarderFeePercentage,
revertError,
});
@ -399,7 +414,7 @@ blockchainTests(ContractName.Forwarder, env => {
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(131, DECIMALS_DEFAULT),
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(20, DECIMALS_DEFAULT),
});
await forwarderTestFactory.marketBuyTestAsync([order], 0.62, erc20Token);
await forwarderTestFactory.marketBuyTestAsync([order], 0.62, [erc20Token]);
});
it('should buy the exact amount of makerAsset in multiple orders', async () => {
const firstOrder = await orderFactory.newSignedOrderAsync();
@ -408,14 +423,29 @@ blockchainTests(ContractName.Forwarder, env => {
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(11, DECIMALS_DEFAULT),
});
const orders = [firstOrder, secondOrder];
await forwarderTestFactory.marketBuyTestAsync(orders, 1.96, erc20Token);
await forwarderTestFactory.marketBuyTestAsync(orders, 1.96, [erc20Token]);
});
it('should buy exactly makerAssetBuyAmount in orders with different makerAssetData', async () => {
const firstOrderMakerAssetData = assetDataUtils.encodeERC20AssetData(erc20Token.address);
const firstOrder = await orderFactory.newSignedOrderAsync({
makerAssetData: firstOrderMakerAssetData,
});
const secondOrderMakerAssetData = assetDataUtils.encodeERC20AssetData(secondErc20Token.address);
const secondOrder = await orderFactory.newSignedOrderAsync({
makerAssetData: secondOrderMakerAssetData,
});
await forwarderWrapper.approveMakerAssetProxyAsync(secondOrderMakerAssetData, { from: takerAddress });
const orders = [firstOrder, secondOrder];
await forwarderTestFactory.marketBuyTestAsync(orders, 1.5, [erc20Token, secondErc20Token]);
});
it('should buy the exact amount of makerAsset and return excess ETH', async () => {
const order = await orderFactory.newSignedOrderAsync({
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(80, DECIMALS_DEFAULT),
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(17, DECIMALS_DEFAULT),
});
await forwarderTestFactory.marketBuyTestAsync([order], 0.57, erc20Token, {
await forwarderTestFactory.marketBuyTestAsync([order], 0.57, [erc20Token], {
ethValueAdjustment: 2,
});
});
@ -426,7 +456,7 @@ blockchainTests(ContractName.Forwarder, env => {
takerFee: Web3Wrapper.toBaseUnitAmount(1, DECIMALS_DEFAULT),
takerFeeAssetData: wethAssetData,
});
await forwarderTestFactory.marketBuyTestAsync([order], 0.38, erc20Token);
await forwarderTestFactory.marketBuyTestAsync([order], 0.38, [erc20Token]);
});
it('should buy the exact amount of makerAsset from a single order with a percentage fee', async () => {
const order = await orderFactory.newSignedOrderAsync({
@ -435,7 +465,7 @@ blockchainTests(ContractName.Forwarder, env => {
takerFee: Web3Wrapper.toBaseUnitAmount(1, DECIMALS_DEFAULT),
takerFeeAssetData: assetDataUtils.encodeERC20AssetData(defaultMakerAssetAddress),
});
await forwarderTestFactory.marketBuyTestAsync([order], 0.52, erc20Token);
await forwarderTestFactory.marketBuyTestAsync([order], 0.52, [erc20Token]);
});
it('should revert if the amount of ETH sent is too low to fill the makerAssetAmount', async () => {
const order = await orderFactory.newSignedOrderAsync();
@ -444,7 +474,7 @@ blockchainTests(ContractName.Forwarder, env => {
constants.ZERO_AMOUNT,
);
await forwarderTestFactory.marketBuyTestAsync([order], 0.5, erc20Token, {
await forwarderTestFactory.marketBuyTestAsync([order], 0.5, [erc20Token], {
ethValueAdjustment: -2,
revertError,
});
@ -456,7 +486,7 @@ blockchainTests(ContractName.Forwarder, env => {
makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId),
takerFeeAssetData: wethAssetData,
});
await forwarderTestFactory.marketBuyTestAsync([erc721Order], 1, erc721Token, {
await forwarderTestFactory.marketBuyTestAsync([erc721Order], 1, [erc721Token], {
makerAssetId,
});
});
@ -468,7 +498,7 @@ blockchainTests(ContractName.Forwarder, env => {
takerFee: Web3Wrapper.toBaseUnitAmount(1, DECIMALS_DEFAULT),
takerFeeAssetData: wethAssetData,
});
await forwarderTestFactory.marketBuyTestAsync([erc721orderWithWethFee], 1, erc721Token, {
await forwarderTestFactory.marketBuyTestAsync([erc721orderWithWethFee], 1, [erc721Token], {
makerAssetId,
});
});
@ -483,14 +513,14 @@ blockchainTests(ContractName.Forwarder, env => {
});
const revertError = new ForwarderRevertErrors.UnsupportedFeeError(takerFeeAssetData);
await forwarderTestFactory.marketBuyTestAsync([order], 0.5, erc20Token, {
await forwarderTestFactory.marketBuyTestAsync([order], 0.5, [erc20Token], {
revertError,
});
});
it('should fill a partially-filled order without a taker fee', async () => {
const order = await orderFactory.newSignedOrderAsync();
await forwarderTestFactory.marketBuyTestAsync([order], 0.3, erc20Token);
await forwarderTestFactory.marketBuyTestAsync([order], 0.8, erc20Token);
await forwarderTestFactory.marketBuyTestAsync([order], 0.3, [erc20Token]);
await forwarderTestFactory.marketBuyTestAsync([order], 0.8, [erc20Token]);
});
it('should skip over an order with an invalid maker asset amount', async () => {
const unfillableOrder = await orderFactory.newSignedOrderAsync({
@ -498,7 +528,7 @@ blockchainTests(ContractName.Forwarder, env => {
});
const fillableOrder = await orderFactory.newSignedOrderAsync();
await forwarderTestFactory.marketBuyTestAsync([unfillableOrder, fillableOrder], 1.5, erc20Token);
await forwarderTestFactory.marketBuyTestAsync([unfillableOrder, fillableOrder], 1.5, [erc20Token]);
});
it('should skip over an order with an invalid taker asset amount', async () => {
const unfillableOrder = await orderFactory.newSignedOrderAsync({
@ -506,7 +536,7 @@ blockchainTests(ContractName.Forwarder, env => {
});
const fillableOrder = await orderFactory.newSignedOrderAsync();
await forwarderTestFactory.marketBuyTestAsync([unfillableOrder, fillableOrder], 1.5, erc20Token);
await forwarderTestFactory.marketBuyTestAsync([unfillableOrder, fillableOrder], 1.5, [erc20Token]);
});
it('should skip over an expired order', async () => {
const currentTimestamp = await getLatestBlockTimestampAsync();
@ -515,21 +545,21 @@ blockchainTests(ContractName.Forwarder, env => {
});
const fillableOrder = await orderFactory.newSignedOrderAsync();
await forwarderTestFactory.marketBuyTestAsync([expiredOrder, fillableOrder], 1.5, erc20Token);
await forwarderTestFactory.marketBuyTestAsync([expiredOrder, fillableOrder], 1.5, [erc20Token]);
});
it('should skip over a fully filled order', async () => {
const fullyFilledOrder = await orderFactory.newSignedOrderAsync();
await forwarderTestFactory.marketBuyTestAsync([fullyFilledOrder], 1, erc20Token);
await forwarderTestFactory.marketBuyTestAsync([fullyFilledOrder], 1, [erc20Token]);
const fillableOrder = await orderFactory.newSignedOrderAsync();
await forwarderTestFactory.marketBuyTestAsync([fullyFilledOrder, fillableOrder], 1.5, erc20Token);
await forwarderTestFactory.marketBuyTestAsync([fullyFilledOrder, fillableOrder], 1.5, [erc20Token]);
});
it('should skip over a cancelled order', async () => {
const cancelledOrder = await orderFactory.newSignedOrderAsync();
await exchangeWrapper.cancelOrderAsync(cancelledOrder, makerAddress);
const fillableOrder = await orderFactory.newSignedOrderAsync();
await forwarderTestFactory.marketBuyTestAsync([cancelledOrder, fillableOrder], 1.5, erc20Token);
await forwarderTestFactory.marketBuyTestAsync([cancelledOrder, fillableOrder], 1.5, [erc20Token]);
});
it('Should buy slightly greater makerAsset when exchange rate is rounded', async () => {
// The 0x Protocol contracts round the exchange rate in favor of the Maker.
@ -649,7 +679,7 @@ blockchainTests(ContractName.Forwarder, env => {
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(125, DECIMALS_DEFAULT),
takerAssetAmount: Web3Wrapper.toBaseUnitAmount(11, DECIMALS_DEFAULT),
});
await forwarderTestFactory.marketBuyTestAsync([order], 0.33, erc20Token, {
await forwarderTestFactory.marketBuyTestAsync([order], 0.33, [erc20Token], {
forwarderFeePercentage: new BigNumber(2),
});
});
@ -658,7 +688,7 @@ blockchainTests(ContractName.Forwarder, env => {
const revertError = new ForwarderRevertErrors.FeePercentageTooLargeError(
ForwarderTestFactory.getPercentageOfValue(constants.PERCENTAGE_DENOMINATOR, new BigNumber(6)),
);
await forwarderTestFactory.marketBuyTestAsync([order], 0.5, erc20Token, {
await forwarderTestFactory.marketBuyTestAsync([order], 0.5, [erc20Token], {
forwarderFeePercentage: new BigNumber(6),
revertError,
});
@ -674,7 +704,7 @@ blockchainTests(ContractName.Forwarder, env => {
const revertError = new ForwarderRevertErrors.InsufficientEthForFeeError(ethFee, ethFee.minus(1));
// -2 to compensate for the extra 1 wei added in ForwarderTestFactory to account for rounding
await forwarderTestFactory.marketBuyTestAsync([order], 0.5, erc20Token, {
await forwarderTestFactory.marketBuyTestAsync([order], 0.5, [erc20Token], {
ethValueAdjustment: -2,
forwarderFeePercentage,
revertError,

View File

@ -3,6 +3,7 @@ import { DummyERC20TokenContract } from '@0x/contracts-erc20';
import { DummyERC721TokenContract } from '@0x/contracts-erc721';
import { ExchangeWrapper } from '@0x/contracts-exchange';
import { constants, ERC20BalancesByOwner, expect, OrderStatus, web3Wrapper } from '@0x/contracts-test-utils';
import { assetDataUtils } from '@0x/order-utils';
import { OrderInfo, SignedOrder } from '@0x/types';
import { BigNumber, RevertError } from '@0x/utils';
import * as _ from 'lodash';
@ -12,10 +13,14 @@ import { ForwarderWrapper } from './forwarder_wrapper';
// Necessary bookkeeping to validate Forwarder results
interface ForwarderFillState {
takerAssetFillAmount: BigNumber;
makerAssetFillAmount: BigNumber;
makerAssetFillAmount: {
[makerAssetData: string]: BigNumber;
};
protocolFees: BigNumber;
wethFees: BigNumber;
percentageFees: BigNumber;
percentageFees: {
[makerAssetData: string]: BigNumber;
};
maxOversoldWeth: BigNumber;
maxOverboughtMakerAsset: BigNumber;
}
@ -51,7 +56,7 @@ export class ForwarderTestFactory {
public async marketBuyTestAsync(
orders: SignedOrder[],
fractionalNumberOfOrdersToFill: number,
makerAssetContract: DummyERC20TokenContract | DummyERC721TokenContract,
makerAssetContracts: Array<DummyERC20TokenContract | DummyERC721TokenContract>,
options: {
ethValueAdjustment?: number; // Used to provided insufficient/excess ETH
forwarderFeePercentage?: BigNumber;
@ -83,9 +88,16 @@ export class ForwarderTestFactory {
constants.PERCENTAGE_DENOMINATOR,
forwarderFeePercentage,
);
const totalMakerAssetFillAmount = Object.values(expectedResults.makerAssetFillAmount).reduce((prev, current) =>
prev.plus(current),
);
const totalPercentageFees = Object.values(expectedResults.percentageFees).reduce((prev, current) =>
prev.plus(current),
);
const tx = this._forwarderWrapper.marketBuyOrdersWithEthAsync(
orders,
expectedResults.makerAssetFillAmount.minus(expectedResults.percentageFees),
totalMakerAssetFillAmount.minus(totalPercentageFees),
{
value: ethValue,
from: this._takerAddress,
@ -110,7 +122,7 @@ export class ForwarderTestFactory {
expectedResults,
takerEthBalanceBefore,
erc20Balances,
makerAssetContract,
makerAssetContracts,
{
forwarderFeePercentage,
forwarderFeeRecipientEthBalanceBefore,
@ -123,7 +135,7 @@ export class ForwarderTestFactory {
public async marketSellTestAsync(
orders: SignedOrder[],
fractionalNumberOfOrdersToFill: number,
makerAssetContract: DummyERC20TokenContract,
makerAssetContracts: DummyERC20TokenContract[],
options: {
forwarderFeePercentage?: BigNumber;
revertError?: RevertError;
@ -179,7 +191,7 @@ export class ForwarderTestFactory {
expectedResults,
takerEthBalanceBefore,
erc20Balances,
makerAssetContract,
makerAssetContracts,
{
forwarderFeePercentage,
forwarderFeeRecipientEthBalanceBefore,
@ -195,31 +207,35 @@ export class ForwarderTestFactory {
makerAssetContract: DummyERC20TokenContract,
): void {
const makerAssetAddress = makerAssetContract.address;
const makerAssetData = assetDataUtils.encodeERC20AssetData(makerAssetAddress);
const {
maxOverboughtMakerAsset,
makerAssetFillAmount: { [makerAssetData]: makerAssetFillAmount },
percentageFees: { [makerAssetData]: percentageFees },
} = expectedResults;
expectBalanceWithin(
newBalances[this._makerAddress][makerAssetAddress],
oldBalances[this._makerAddress][makerAssetAddress]
.minus(expectedResults.makerAssetFillAmount)
.minus(expectedResults.maxOverboughtMakerAsset),
oldBalances[this._makerAddress][makerAssetAddress].minus(expectedResults.makerAssetFillAmount),
.minus(makerAssetFillAmount)
.minus(maxOverboughtMakerAsset),
oldBalances[this._makerAddress][makerAssetAddress].minus(makerAssetFillAmount),
'Maker makerAsset balance',
);
expectBalanceWithin(
newBalances[this._takerAddress][makerAssetAddress],
oldBalances[this._takerAddress][makerAssetAddress].plus(makerAssetFillAmount).minus(percentageFees),
oldBalances[this._takerAddress][makerAssetAddress]
.plus(expectedResults.makerAssetFillAmount)
.minus(expectedResults.percentageFees),
oldBalances[this._takerAddress][makerAssetAddress]
.plus(expectedResults.makerAssetFillAmount)
.minus(expectedResults.percentageFees)
.plus(expectedResults.maxOverboughtMakerAsset),
.plus(makerAssetFillAmount)
.minus(percentageFees)
.plus(maxOverboughtMakerAsset),
'Taker makerAsset balance',
);
expect(
newBalances[this._orderFeeRecipientAddress][makerAssetAddress],
'Order fee recipient makerAsset balance',
).to.be.bignumber.equal(
oldBalances[this._orderFeeRecipientAddress][makerAssetAddress].plus(expectedResults.percentageFees),
);
).to.be.bignumber.equal(oldBalances[this._orderFeeRecipientAddress][makerAssetAddress].plus(percentageFees));
expect(
newBalances[this._forwarderAddress][makerAssetAddress],
'Forwarder contract makerAsset balance',
@ -234,7 +250,7 @@ export class ForwarderTestFactory {
expectedResults: ForwarderFillState,
takerEthBalanceBefore: BigNumber,
erc20Balances: ERC20BalancesByOwner,
makerAssetContract: DummyERC20TokenContract | DummyERC721TokenContract,
makerAssetContracts: Array<DummyERC20TokenContract | DummyERC721TokenContract>,
options: {
forwarderFeePercentage?: BigNumber;
forwarderFeeRecipientEthBalanceBefore?: BigNumber;
@ -277,12 +293,14 @@ export class ForwarderTestFactory {
);
}
for (const makerAssetContract of makerAssetContracts) {
if (makerAssetContract instanceof DummyERC20TokenContract) {
this._checkErc20Balances(erc20Balances, newBalances, expectedResults, makerAssetContract);
} else if (options.makerAssetId !== undefined) {
const newOwner = await makerAssetContract.ownerOf.callAsync(options.makerAssetId);
expect(newOwner, 'New ERC721 owner').to.be.bignumber.equal(this._takerAddress);
}
}
expectBalanceWithin(
newBalances[this._makerAddress][this._wethAddress],
@ -313,18 +331,25 @@ export class ForwarderTestFactory {
ordersInfoBefore: OrderInfo[],
fractionalNumberOfOrdersToFill: number,
): ForwarderFillState {
const currentState = {
const currentState: ForwarderFillState = {
takerAssetFillAmount: constants.ZERO_AMOUNT,
makerAssetFillAmount: constants.ZERO_AMOUNT,
makerAssetFillAmount: {},
protocolFees: constants.ZERO_AMOUNT,
wethFees: constants.ZERO_AMOUNT,
percentageFees: constants.ZERO_AMOUNT,
percentageFees: {},
maxOversoldWeth: constants.ZERO_AMOUNT,
maxOverboughtMakerAsset: constants.ZERO_AMOUNT,
};
let remainingOrdersToFill = fractionalNumberOfOrdersToFill;
for (const [i, order] of orders.entries()) {
if (currentState.makerAssetFillAmount[order.makerAssetData] === undefined) {
currentState.makerAssetFillAmount[order.makerAssetData] = new BigNumber(0);
}
if (currentState.percentageFees[order.makerAssetData] === undefined) {
currentState.percentageFees[order.makerAssetData] = new BigNumber(0);
}
if (remainingOrdersToFill === 0) {
break;
}
@ -365,7 +390,9 @@ export class ForwarderTestFactory {
makerAssetAmount = BigNumber.max(makerAssetAmount.minus(makerAssetFilled), constants.ZERO_AMOUNT);
currentState.takerAssetFillAmount = currentState.takerAssetFillAmount.plus(takerAssetAmount);
currentState.makerAssetFillAmount = currentState.makerAssetFillAmount.plus(makerAssetAmount);
currentState.makerAssetFillAmount[order.makerAssetData] = currentState.makerAssetFillAmount[
order.makerAssetData
].plus(makerAssetAmount);
if (this._protocolFeeCollectorAddress !== constants.NULL_ADDRESS) {
currentState.protocolFees = currentState.protocolFees.plus(
@ -373,7 +400,9 @@ export class ForwarderTestFactory {
);
}
if (order.takerFeeAssetData === order.makerAssetData) {
currentState.percentageFees = currentState.percentageFees.plus(takerFee);
currentState.percentageFees[order.makerAssetData] = currentState.percentageFees[
order.makerAssetData
].plus(takerFee);
} else if (order.takerFeeAssetData === order.takerAssetData) {
currentState.wethFees = currentState.wethFees.plus(takerFee);
}