Make marketBuy functions revert if entire amount not filled

This commit is contained in:
Amir Bandeali 2018-08-09 11:20:06 -07:00
parent b60a74c8bc
commit b9d8d2d5e3
6 changed files with 44 additions and 141 deletions

View File

@ -139,7 +139,7 @@ contract MixinExchangeWrapper is
/// @param makerAssetFillAmount Desired amount of makerAsset to buy.
/// @param signatures Proofs that orders have been signed by makers.
/// @return Amounts filled and fees paid by makers and taker.
function marketBuyWithWeth(
function marketBuyExactAmountWithWeth(
LibOrder.Order[] memory orders,
uint256 makerAssetFillAmount,
bytes[] memory signatures
@ -180,10 +180,16 @@ contract MixinExchangeWrapper is
addFillResults(totalFillResults, singleFillResults);
// Stop execution if the entire amount of makerAsset has been bought
if (totalFillResults.makerAssetFilledAmount >= makerAssetFillAmount) {
uint256 makerAssetFilledAmount = totalFillResults.makerAssetFilledAmount;
if (makerAssetFilledAmount >= makerAssetFillAmount) {
break;
}
}
require(
makerAssetFilledAmount >= makerAssetFillAmount,
"COMPLETE_FILL_FAILED"
);
return totalFillResults;
}
@ -196,7 +202,7 @@ contract MixinExchangeWrapper is
/// @param zrxBuyAmount Desired amount of ZRX to buy.
/// @param signatures Proofs that orders have been created by makers.
/// @return totalFillResults Amounts filled and fees paid by maker and taker.
function marketBuyZrxWithWeth(
function marketBuyExactZrxWithWeth(
LibOrder.Order[] memory orders,
uint256 zrxBuyAmount,
bytes[] memory signatures
@ -248,6 +254,10 @@ contract MixinExchangeWrapper is
}
}
require(
zrxPurchased >= zrxBuyAmount,
"COMPLETE_FILL_FAILED"
);
return totalFillResults;
}
}

View File

@ -23,7 +23,7 @@ import "./libs/LibConstants.sol";
import "./mixins/MWeth.sol";
import "./mixins/MAssets.sol";
import "./mixins/MExchangeWrapper.sol";
import "./mixins/MForwarderCore.sol";
import "./interfaces/IForwarderCore.sol";
import "../utils/LibBytes/LibBytes.sol";
import "../protocol/Exchange/libs/LibOrder.sol";
import "../protocol/Exchange/libs/LibFillResults.sol";
@ -37,7 +37,7 @@ contract MixinForwarderCore is
MWeth,
MAssets,
MExchangeWrapper,
MForwarderCore
IForwarderCore
{
using LibBytes for bytes;
@ -117,7 +117,7 @@ contract MixinForwarderCore is
);
// Buy back all ZRX spent on fees.
zrxBuyAmount = orderFillResults.takerFeePaid;
feeOrderFillResults = marketBuyZrxWithWeth(
feeOrderFillResults = marketBuyExactZrxWithWeth(
feeOrders,
zrxBuyAmount,
feeSignatures
@ -125,13 +125,6 @@ contract MixinForwarderCore is
makerAssetAmountPurchased = orderFillResults.makerAssetFilledAmount;
}
// Ensure that all ZRX fees have been repurchased and no extra WETH owned by this contract has been sold.
assertValidFillResults(
orderFillResults,
feeOrderFillResults,
zrxBuyAmount
);
// Transfer feePercentage of total ETH spent on primary orders to feeRecipient.
// Refund remaining ETH to msg.sender.
transferEthFeeAndRefund(
@ -180,7 +173,7 @@ contract MixinForwarderCore is
if (orders[0].makerAssetData.equals(ZRX_ASSET_DATA)) {
// If the makerAsset is ZRX, it is not necessary to pay fees out of this
// contracts's ZRX balance because fees are factored into the price of the order.
orderFillResults = marketBuyZrxWithWeth(
orderFillResults = marketBuyExactZrxWithWeth(
orders,
makerAssetFillAmount,
signatures
@ -190,14 +183,14 @@ contract MixinForwarderCore is
} else {
// Attemp to purchase desired amount of makerAsset.
// ZRX fees are payed with this contract's balance.
orderFillResults = marketBuyWithWeth(
orderFillResults = marketBuyExactAmountWithWeth(
orders,
makerAssetFillAmount,
signatures
);
// Buy back all ZRX spent on fees.
zrxBuyAmount = orderFillResults.takerFeePaid;
feeOrderFillResults = marketBuyZrxWithWeth(
feeOrderFillResults = marketBuyExactZrxWithWeth(
feeOrders,
zrxBuyAmount,
feeSignatures
@ -205,13 +198,6 @@ contract MixinForwarderCore is
makerAssetAmountPurchased = orderFillResults.makerAssetFilledAmount;
}
// Ensure that all ZRX fees have been repurchased and no extra WETH owned by this contract has been sold.
assertValidFillResults(
orderFillResults,
feeOrderFillResults,
zrxBuyAmount
);
// Transfer feePercentage of total ETH spent on primary orders to feeRecipient.
// Refund remaining ETH to msg.sender.
transferEthFeeAndRefund(
@ -224,31 +210,4 @@ contract MixinForwarderCore is
// Transfer purchased assets to msg.sender.
transferPurchasedAssetToSender(orders[0].makerAssetData, makerAssetAmountPurchased);
}
/// @dev Ensures that all ZRX fees have been repurchased and no extra WETH owned by this contract has been sold.
/// @param orderFillResults Amounts filled and fees paid for primary orders.
/// @param feeOrderFillResults Amounts filled and fees paid for fee orders.
/// @param zrxBuyAmount The amount of ZRX that needed to be repurchased after filling primary orders.
function assertValidFillResults(
FillResults memory orderFillResults,
FillResults memory feeOrderFillResults,
uint256 zrxBuyAmount
)
internal
view
{
// Ensure that all ZRX spent while filling primary orders has been repurchased.
uint256 zrxPurchased = safeSub(feeOrderFillResults.makerAssetFilledAmount, feeOrderFillResults.takerFeePaid);
require(
zrxPurchased >= zrxBuyAmount,
"COMPLETE_FILL_FAILED"
);
// Ensure that no extra WETH owned by this contract has been sold.
uint256 wethSold = safeAdd(orderFillResults.takerAssetFilledAmount, feeOrderFillResults.takerAssetFilledAmount);
require(
wethSold <= msg.value,
"OVERSOLD_WETH"
);
}
}

View File

@ -71,12 +71,16 @@ contract MixinWeth is
"FEE_PERCENTAGE_TOO_LARGE"
);
// Calculate amount of WETH that hasn't been sold.
uint256 wethRemaining = safeSub(
msg.value,
safeAdd(wethSoldExcludingFeeOrders, wethSoldForZrx)
// Ensure that no extra WETH owned by this contract has been sold.
uint256 wethSold = safeAdd(wethSoldExcludingFeeOrders, wethSoldForZrx);
require(
wethSold <= msg.value,
"OVERSOLD_WETH"
);
// Calculate amount of WETH that hasn't been sold.
uint256 wethRemaining = safeSub(msg.value, wethSold);
// Calculate ETH fee to pay to feeRecipient.
uint256 ethFee = getPartialAmount(
feePercentage,

View File

@ -60,7 +60,7 @@ contract MExchangeWrapper {
/// @param makerAssetFillAmount Desired amount of makerAsset to buy.
/// @param signatures Proofs that orders have been signed by makers.
/// @return Amounts filled and fees paid by makers and taker.
function marketBuyWithWeth(
function marketBuyExactAmountWithWeth(
LibOrder.Order[] memory orders,
uint256 makerAssetFillAmount,
bytes[] memory signatures
@ -77,7 +77,7 @@ contract MExchangeWrapper {
/// @param zrxBuyAmount Desired amount of ZRX to buy.
/// @param signatures Proofs that orders have been created by makers.
/// @return totalFillResults Amounts filled and fees paid by maker and taker.
function marketBuyZrxWithWeth(
function marketBuyExactZrxWithWeth(
LibOrder.Order[] memory orders,
uint256 zrxBuyAmount,
bytes[] memory signatures

View File

@ -1,42 +0,0 @@
/*
Copyright 2018 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.4.24;
pragma experimental ABIEncoderV2;
import "../../protocol/Exchange/libs/LibOrder.sol";
import "../../protocol/Exchange/libs/LibFillResults.sol";
import "../interfaces/IForwarderCore.sol";
contract MForwarderCore is
IForwarderCore
{
/// @dev Ensures that all ZRX fees have been repurchased and no extra WETH owned by this contract has been sold.
/// @param orderFillResults Amounts filled and fees paid for primary orders.
/// @param feeOrderFillResults Amounts filled and fees paid for fee orders.
/// @param zrxBuyAmount The amount of ZRX that needed to be repurchased after filling primary orders.
function assertValidFillResults(
LibFillResults.FillResults memory orderFillResults,
LibFillResults.FillResults memory feeOrderFillResults,
uint256 zrxBuyAmount
)
internal
view;
}

View File

@ -722,25 +722,18 @@ describe(ContractName.Forwarder, () => {
);
expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
});
it('should not change balances if the amount of ETH sent is too low to fill the makerAssetAmount', async () => {
it('should revert if the amount of ETH sent is too low to fill the makerAssetAmount', async () => {
const ordersWithoutFee = [orderWithoutFee];
const feeOrders: SignedOrder[] = [];
const makerAssetFillAmount = orderWithoutFee.makerAssetAmount.dividedToIntegerBy(2);
const ethValue = orderWithoutFee.takerAssetAmount.dividedToIntegerBy(4);
tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(ordersWithoutFee, feeOrders, makerAssetFillAmount, {
value: ethValue,
from: takerAddress,
});
const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
const newBalances = await erc20Wrapper.getBalancesAsync();
const totalEthSpent = gasPrice.times(tx.gasUsed);
expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
expect(newBalances).to.deep.equal(erc20Balances);
expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
return expectTransactionFailedAsync(
forwarderWrapper.marketBuyOrdersWithEthAsync(ordersWithoutFee, feeOrders, makerAssetFillAmount, {
value: ethValue,
from: takerAddress,
}),
RevertReason.CompleteFillFailed,
);
});
it('should buy an ERC721 asset from a single order', async () => {
const makerAssetId = erc721MakerAssetIds[0];
@ -775,7 +768,7 @@ describe(ContractName.Forwarder, () => {
);
expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
});
it('should buy an ERC721 asset and ignore later orders with different makerAssetData', async () => {
it('should revert if buying an ERC721 asset when later orders contain different makerAssetData', async () => {
const makerAssetId = erc721MakerAssetIds[0];
orderWithoutFee = await orderFactory.newSignedOrderAsync({
makerAssetAmount: new BigNumber(1),
@ -786,33 +779,12 @@ describe(ContractName.Forwarder, () => {
const feeOrders: SignedOrder[] = [];
const makerAssetFillAmount = new BigNumber(1).plus(differentMakerAssetDataOrder.makerAssetAmount);
const ethValue = orderWithFee.takerAssetAmount;
tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(ordersWithoutFee, feeOrders, makerAssetFillAmount, {
from: takerAddress,
value: ethValue,
});
const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
const newOwner = await erc721Token.ownerOf.callAsync(makerAssetId);
const newBalances = await erc20Wrapper.getBalancesAsync();
const primaryTakerAssetFillAmount = ethValue;
const totalEthSpent = primaryTakerAssetFillAmount.plus(gasPrice.times(tx.gasUsed));
expect(newOwner).to.be.bignumber.equal(takerAddress);
expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount),
);
expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
expect(newBalances[forwarderContract.address][defaultMakerAssetAddress]).to.be.bignumber.equal(
constants.ZERO_AMOUNT,
);
expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
erc20Balances[makerAddress][defaultMakerAssetAddress],
);
expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
erc20Balances[takerAddress][defaultMakerAssetAddress],
return expectTransactionFailedAsync(
forwarderWrapper.marketBuyOrdersWithEthAsync(ordersWithoutFee, feeOrders, makerAssetFillAmount, {
value: ethValue,
from: takerAddress,
}),
RevertReason.CompleteFillFailed,
);
});
it('should buy an ERC721 asset and pay ZRX fees from a single fee order', async () => {