Make marketBuy functions revert if entire amount not filled
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
@@ -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"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -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,
|
||||
|
@@ -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
|
||||
|
@@ -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;
|
||||
}
|
@@ -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, {
|
||||
return expectTransactionFailedAsync(
|
||||
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);
|
||||
}),
|
||||
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,
|
||||
return expectTransactionFailedAsync(
|
||||
forwarderWrapper.marketBuyOrdersWithEthAsync(ordersWithoutFee, feeOrders, makerAssetFillAmount, {
|
||||
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],
|
||||
from: takerAddress,
|
||||
}),
|
||||
RevertReason.CompleteFillFailed,
|
||||
);
|
||||
});
|
||||
it('should buy an ERC721 asset and pay ZRX fees from a single fee order', async () => {
|
||||
|
Reference in New Issue
Block a user