aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAmir Bandeali <abandeali1@gmail.com>2018-07-23 12:04:49 +0800
committerAmir Bandeali <abandeali1@gmail.com>2018-07-23 23:02:51 +0800
commitdcc0908617b83de8f45af3e9ca6854b897be3d72 (patch)
tree061bbbc74e77143819aca9d9de3cde59566b5972
parent45d68285f14ba328e1bf5a8cc9e59cdb39d7f306 (diff)
downloaddexon-0x-contracts-dcc0908617b83de8f45af3e9ca6854b897be3d72.tar.gz
dexon-0x-contracts-dcc0908617b83de8f45af3e9ca6854b897be3d72.tar.zst
dexon-0x-contracts-dcc0908617b83de8f45af3e9ca6854b897be3d72.zip
Add more tests and fixes
-rw-r--r--packages/contracts/src/2.0.0/forwarder/MixinExchangeWrapper.sol5
-rw-r--r--packages/contracts/src/2.0.0/forwarder/MixinWeth.sol2
-rw-r--r--packages/contracts/src/2.0.0/forwarder/libs/LibConstants.sol2
-rw-r--r--packages/contracts/src/2.0.0/forwarder/libs/LibForwarderErrors.sol2
-rw-r--r--packages/contracts/src/2.0.0/utils/SafeMath/SafeMath.sol2
-rw-r--r--packages/contracts/test/forwarder/forwarder.ts877
-rw-r--r--packages/contracts/test/utils/forwarder_wrapper.ts4
-rw-r--r--packages/types/src/index.ts2
8 files changed, 484 insertions, 412 deletions
diff --git a/packages/contracts/src/2.0.0/forwarder/MixinExchangeWrapper.sol b/packages/contracts/src/2.0.0/forwarder/MixinExchangeWrapper.sol
index d80e06350..e230767b7 100644
--- a/packages/contracts/src/2.0.0/forwarder/MixinExchangeWrapper.sol
+++ b/packages/contracts/src/2.0.0/forwarder/MixinExchangeWrapper.sol
@@ -100,12 +100,15 @@ contract MixinExchangeWrapper is
internal
returns (FillResults memory totalFillResults)
{
+ bytes memory makerAssetData = orders[0].makerAssetData;
bytes memory wethAssetData = WETH_ASSET_DATA;
uint256 ordersLength = orders.length;
for (uint256 i = 0; i < ordersLength; i++) {
+ // We assume that asset being bought by taker is the same for each order.
// We assume that asset being sold by taker is WETH for each order.
+ orders[i].makerAssetData = makerAssetData;
orders[i].takerAssetData = wethAssetData;
// Calculate the remaining amount of WETH to sell
@@ -231,7 +234,7 @@ contract MixinExchangeWrapper is
// Attempt to sell the remaining amount of WETH.
FillResults memory singleFillResult = fillOrderNoThrow(
orders[i],
- safeAdd(remainingWethSellAmount, 1),
+ safeAdd(remainingWethSellAmount, 1), // we add 1 wei to the fill amount to make up for rounding errors
signatures[i]
);
diff --git a/packages/contracts/src/2.0.0/forwarder/MixinWeth.sol b/packages/contracts/src/2.0.0/forwarder/MixinWeth.sol
index a50863a59..8ba236e7f 100644
--- a/packages/contracts/src/2.0.0/forwarder/MixinWeth.sol
+++ b/packages/contracts/src/2.0.0/forwarder/MixinWeth.sol
@@ -87,7 +87,7 @@ contract MixinWeth is
// Ensure fee is less than amount of WETH remaining.
require(
ethFee <= wethRemaining,
- "MAX_FEE_EXCEEDED"
+ "INSUFFICIENT_ETH_REMAINING"
);
// Do nothing if no WETH remaining
diff --git a/packages/contracts/src/2.0.0/forwarder/libs/LibConstants.sol b/packages/contracts/src/2.0.0/forwarder/libs/LibConstants.sol
index 8abe1e42d..c26d7902c 100644
--- a/packages/contracts/src/2.0.0/forwarder/libs/LibConstants.sol
+++ b/packages/contracts/src/2.0.0/forwarder/libs/LibConstants.sol
@@ -26,7 +26,7 @@ import "../../tokens/ERC20Token/IERC20Token.sol";
contract LibConstants {
bytes4 constant internal ERC20_DATA_ID = bytes4(keccak256("ERC20Token(address)"));
- bytes4 constant internal ERC721_DATA_ID = bytes4(keccak256("ERC721Token(address,uint256,bytes)"));
+ bytes4 constant internal ERC721_DATA_ID = bytes4(keccak256("ERC721Token(address,uint256)"));
uint256 constant internal MAX_UINT = 2**256 - 1;
uint256 constant internal PERCENTAGE_DENOMINATOR = 10**18;
uint256 constant internal MAX_FEE_PERCENTAGE = 5 * PERCENTAGE_DENOMINATOR / 100; // 5%
diff --git a/packages/contracts/src/2.0.0/forwarder/libs/LibForwarderErrors.sol b/packages/contracts/src/2.0.0/forwarder/libs/LibForwarderErrors.sol
index 69108738a..cdfb77a0b 100644
--- a/packages/contracts/src/2.0.0/forwarder/libs/LibForwarderErrors.sol
+++ b/packages/contracts/src/2.0.0/forwarder/libs/LibForwarderErrors.sol
@@ -23,7 +23,7 @@ pragma solidity 0.4.24;
/// This contract is intended to serve as a reference, but is not actually used for efficiency reasons.
contract LibForwarderErrors {
string constant FEE_PERCENTAGE_TOO_LARGE = "FEE_PROPORTION_TOO_LARGE"; // Provided fee percentage greater than 5%.
- string constant MAX_FEE_EXCEEDED = "MAX_FEE_EXCEEDED"; // Not enough ETH remaining to pay feeRecipient.
+ string constant INSUFFICIENT_ETH_REMAINING = "INSUFFICIENT_ETH_REMAINING"; // Not enough ETH remaining to pay feeRecipient.
string constant OVERSOLD_WETH = "OVERSOLD_WETH"; // More WETH sold than provided with current message call.
string constant COMPLETE_FILL_FAILED = "COMPLETE_FILL_FAILED"; // Desired purchase amount not completely filled (required for ZRX fees only).
string constant TRANSFER_FAILED = "TRANSFER_FAILED"; // Asset transfer failed.
diff --git a/packages/contracts/src/2.0.0/utils/SafeMath/SafeMath.sol b/packages/contracts/src/2.0.0/utils/SafeMath/SafeMath.sol
index 190989181..63a2a085f 100644
--- a/packages/contracts/src/2.0.0/utils/SafeMath/SafeMath.sol
+++ b/packages/contracts/src/2.0.0/utils/SafeMath/SafeMath.sol
@@ -34,7 +34,7 @@ contract SafeMath {
{
require(
b <= a,
- "UINT256_OVERFLOW"
+ "UINT256_UNDERFLOW"
);
return a - b;
}
diff --git a/packages/contracts/test/forwarder/forwarder.ts b/packages/contracts/test/forwarder/forwarder.ts
index f10f22556..b6d81df58 100644
--- a/packages/contracts/test/forwarder/forwarder.ts
+++ b/packages/contracts/test/forwarder/forwarder.ts
@@ -27,8 +27,9 @@ chaiSetup.configure();
const expect = chai.expect;
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
const DECIMALS_DEFAULT = 18;
+const MAX_WETH_FILL_PERCENTAGE = 95;
-describe(ContractName.Forwarder, () => {
+describe.only(ContractName.Forwarder, () => {
let makerAddress: string;
let owner: string;
let takerAddress: string;
@@ -45,11 +46,8 @@ describe(ContractName.Forwarder, () => {
let exchangeWrapper: ExchangeWrapper;
let orderWithoutFee: SignedOrder;
- let ordersWithoutFee: SignedOrder[];
let orderWithFee: SignedOrder;
- let ordersWithFee: SignedOrder[];
let feeOrder: SignedOrder;
- let feeOrders: SignedOrder[];
let orderFactory: OrderFactory;
let erc20Wrapper: ERC20Wrapper;
let erc20Balances: ERC20BalancesByOwner;
@@ -60,8 +58,6 @@ describe(ContractName.Forwarder, () => {
let feePercentage: BigNumber;
let gasPrice: BigNumber;
- const MAX_WETH_FILL_PERCENTAGE = 95;
-
before(async () => {
await blockchainLifecycle.startAsync();
const accounts = await web3Wrapper.getAvailableAddressesAsync();
@@ -168,9 +164,9 @@ describe(ContractName.Forwarder, () => {
});
describe('marketSellOrdersWithEth without extra fees', () => {
- it('should fill the order', async () => {
- ordersWithoutFee = [orderWithoutFee];
- feeOrders = [];
+ it('should fill a single order', async () => {
+ const ordersWithoutFee = [orderWithoutFee];
+ const feeOrders: SignedOrder[] = [];
const ethValue = orderWithoutFee.takerAssetAmount.dividedToIntegerBy(2);
tx = await forwarderWrapper.marketSellOrdersWithEthAsync(ordersWithoutFee, feeOrders, {
@@ -206,9 +202,54 @@ describe(ContractName.Forwarder, () => {
);
expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
});
- it('should fill the order and pay ZRX fees from feeOrders', async () => {
- ordersWithFee = [orderWithFee];
- feeOrders = [feeOrder];
+ it('should fill multiple orders', async () => {
+ const secondOrderWithoutFee = await orderFactory.newSignedOrderAsync();
+ const ordersWithoutFee = [orderWithoutFee, secondOrderWithoutFee];
+ const feeOrders: SignedOrder[] = [];
+ const ethValue = ordersWithoutFee[0].takerAssetAmount.plus(
+ ordersWithoutFee[1].takerAssetAmount.dividedToIntegerBy(2),
+ );
+
+ tx = await forwarderWrapper.marketSellOrdersWithEthAsync(ordersWithoutFee, feeOrders, {
+ value: ethValue,
+ from: takerAddress,
+ });
+ const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
+ const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+
+ const primaryTakerAssetFillAmount = ForwarderWrapper.getPercentageOfValue(
+ ethValue,
+ MAX_WETH_FILL_PERCENTAGE,
+ );
+ const firstTakerAssetFillAmount = ordersWithoutFee[0].takerAssetAmount;
+ const secondTakerAssetFillAmount = primaryTakerAssetFillAmount.minus(firstTakerAssetFillAmount);
+
+ const makerAssetFillAmount = ordersWithoutFee[0].makerAssetAmount.plus(
+ ordersWithoutFee[1].makerAssetAmount
+ .times(secondTakerAssetFillAmount)
+ .dividedToIntegerBy(ordersWithoutFee[1].takerAssetAmount),
+ );
+ const totalEthSpent = primaryTakerAssetFillAmount.plus(gasPrice.times(tx.gasUsed));
+ expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
+ expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
+ );
+ expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFillAmount),
+ );
+ 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);
+ });
+ it('should fill the order and pay ZRX fees from a single feeOrder', async () => {
+ const ordersWithFee = [orderWithFee];
+ const feeOrders = [feeOrder];
const ethValue = orderWithFee.takerAssetAmount.dividedToIntegerBy(2);
tx = await forwarderWrapper.marketSellOrdersWithEthAsync(ordersWithFee, feeOrders, {
@@ -251,13 +292,71 @@ describe(ContractName.Forwarder, () => {
);
expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
});
+ it('should fill the orders and pay ZRX from multiple feeOrders', async () => {
+ const ordersWithFee = [orderWithFee];
+ const ethValue = orderWithFee.takerAssetAmount;
+ const makerAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address);
+ const makerAssetAmount = orderWithFee.takerFee.dividedToIntegerBy(2);
+ const takerAssetAmount = feeOrder.takerAssetAmount
+ .times(makerAssetAmount)
+ .dividedToIntegerBy(feeOrder.makerAssetAmount);
+
+ const firstFeeOrder = await orderFactory.newSignedOrderAsync({
+ makerAssetData,
+ makerAssetAmount,
+ takerAssetAmount,
+ });
+ const secondFeeOrder = await orderFactory.newSignedOrderAsync({
+ makerAssetData,
+ makerAssetAmount,
+ takerAssetAmount,
+ });
+ const feeOrders = [firstFeeOrder, secondFeeOrder];
+
+ tx = await forwarderWrapper.marketSellOrdersWithEthAsync(ordersWithFee, feeOrders, {
+ value: ethValue,
+ from: takerAddress,
+ });
+ const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
+ const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+
+ const primaryTakerAssetFillAmount = ForwarderWrapper.getPercentageOfValue(
+ ethValue,
+ MAX_WETH_FILL_PERCENTAGE,
+ );
+ const makerAssetFillAmount = primaryTakerAssetFillAmount
+ .times(orderWithoutFee.makerAssetAmount)
+ .dividedToIntegerBy(orderWithoutFee.takerAssetAmount);
+ const feeAmount = ForwarderWrapper.getPercentageOfValue(orderWithFee.takerFee, MAX_WETH_FILL_PERCENTAGE);
+ const wethSpentOnFeeOrders = ForwarderWrapper.getWethForFeeOrders(feeAmount, feeOrders);
+ const totalEthSpent = primaryTakerAssetFillAmount
+ .plus(wethSpentOnFeeOrders)
+ .plus(gasPrice.times(tx.gasUsed));
+
+ expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
+ expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
+ );
+ expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFillAmount),
+ );
+ expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount).plus(wethSpentOnFeeOrders),
+ );
+ 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);
+ });
it('should fill the order when token is ZRX with fees', async () => {
orderWithFee = await orderFactory.newSignedOrderAsync({
makerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address),
takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT),
});
- ordersWithFee = [orderWithFee];
- feeOrders = [];
+ const ordersWithFee = [orderWithFee];
+ const feeOrders: SignedOrder[] = [];
const ethValue = orderWithFee.takerAssetAmount.dividedToIntegerBy(2);
tx = await forwarderWrapper.marketSellOrdersWithEthAsync(ordersWithFee, feeOrders, {
@@ -290,7 +389,8 @@ describe(ContractName.Forwarder, () => {
expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
});
it('should refund remaining ETH if amount is greater than takerAssetAmount', async () => {
- ordersWithoutFee = [orderWithoutFee];
+ const ordersWithoutFee = [orderWithoutFee];
+ const feeOrders: SignedOrder[] = [];
const ethValue = orderWithoutFee.takerAssetAmount.times(2);
tx = await forwarderWrapper.marketSellOrdersWithEthAsync(ordersWithoutFee, feeOrders, {
@@ -306,12 +406,12 @@ describe(ContractName.Forwarder, () => {
orderWithFee = await orderFactory.newSignedOrderAsync({
takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(50), DECIMALS_DEFAULT),
});
- ordersWithFee = [orderWithFee];
+ const ordersWithFee = [orderWithFee];
feeOrder = await orderFactory.newSignedOrderAsync({
makerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address),
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT),
});
- feeOrders = [feeOrder];
+ const feeOrders = [feeOrder];
const ethValue = orderWithFee.takerAssetAmount;
return expectTransactionFailedAsync(
forwarderWrapper.marketSellOrdersWithEthAsync(ordersWithFee, feeOrders, {
@@ -328,7 +428,8 @@ describe(ContractName.Forwarder, () => {
makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId),
});
const erc20SignedOrder = await orderFactory.newSignedOrderAsync();
- ordersWithoutFee = [erc20SignedOrder, erc721SignedOrder];
+ const ordersWithoutFee = [erc20SignedOrder, erc721SignedOrder];
+ const feeOrders: SignedOrder[] = [];
const ethValue = erc20SignedOrder.takerAssetAmount.plus(erc721SignedOrder.takerAssetAmount);
tx = await forwarderWrapper.marketSellOrdersWithEthAsync(ordersWithoutFee, feeOrders, {
@@ -343,8 +444,8 @@ describe(ContractName.Forwarder, () => {
});
describe('marketSellOrdersWithEth with extra fees', () => {
it('should fill the order and send fee to feeRecipient', async () => {
- ordersWithoutFee = [orderWithoutFee];
- feeOrders = [];
+ const ordersWithoutFee = [orderWithoutFee];
+ const feeOrders: SignedOrder[] = [];
const ethValue = orderWithoutFee.takerAssetAmount.div(2);
const baseFeePercentage = 2;
@@ -392,11 +493,11 @@ describe(ContractName.Forwarder, () => {
expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
});
it('should fail if the fee is set too high', async () => {
- const feeRecipientEthBalanceBefore = await web3Wrapper.getBalanceInWeiAsync(feeRecipientAddress);
const ethValue = orderWithoutFee.takerAssetAmount.div(2);
const baseFeePercentage = 6;
feePercentage = ForwarderWrapper.getPercentageOfValue(ethValue, baseFeePercentage);
- feeOrders = [];
+ const ordersWithoutFee = [orderWithoutFee];
+ const feeOrders: SignedOrder[] = [];
await expectTransactionFailedAsync(
forwarderWrapper.marketSellOrdersWithEthAsync(
ordersWithoutFee,
@@ -406,14 +507,28 @@ describe(ContractName.Forwarder, () => {
),
RevertReason.FeePercentageTooLarge,
);
- const feeRecipientEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(feeRecipientAddress);
- expect(feeRecipientEthBalanceAfter).to.be.bignumber.equal(feeRecipientEthBalanceBefore);
+ });
+ it('should fail if there is not enough ETH remaining to pay the fee', async () => {
+ const ethValue = orderWithoutFee.takerAssetAmount.div(2);
+ const baseFeePercentage = 5;
+ feePercentage = ForwarderWrapper.getPercentageOfValue(constants.PERCENTAGE_DENOMINATOR, baseFeePercentage);
+ const ordersWithFee = [orderWithFee];
+ const feeOrders = [feeOrder];
+ await expectTransactionFailedAsync(
+ forwarderWrapper.marketSellOrdersWithEthAsync(
+ ordersWithFee,
+ feeOrders,
+ { from: takerAddress, value: ethValue, gasPrice },
+ { feePercentage, feeRecipient: feeRecipientAddress },
+ ),
+ RevertReason.InsufficientEthRemaining,
+ );
});
});
describe('marketBuyOrdersWithEth without extra fees', () => {
- it('should buy the exact amount of assets', async () => {
- ordersWithoutFee = [orderWithoutFee];
- feeOrders = [];
+ it('should buy the exact amount of makerAsset in a single order', async () => {
+ const ordersWithoutFee = [orderWithoutFee];
+ const feeOrders: SignedOrder[] = [];
const makerAssetFillAmount = orderWithoutFee.makerAssetAmount.dividedToIntegerBy(2);
const ethValue = orderWithoutFee.takerAssetAmount.dividedToIntegerBy(2);
@@ -444,9 +559,47 @@ describe(ContractName.Forwarder, () => {
);
expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
});
- it('should buy the exact amount of assets and return excess ETH', async () => {
- ordersWithoutFee = [orderWithoutFee];
- feeOrders = [];
+ it('should buy the exact amount of makerAsset in multiple orders', async () => {
+ const secondOrderWithoutFee = await orderFactory.newSignedOrderAsync();
+ const ordersWithoutFee = [orderWithoutFee, secondOrderWithoutFee];
+ const feeOrders: SignedOrder[] = [];
+ const makerAssetFillAmount = ordersWithoutFee[0].makerAssetAmount.plus(
+ ordersWithoutFee[1].makerAssetAmount.dividedToIntegerBy(2),
+ );
+ const ethValue = ordersWithoutFee[0].takerAssetAmount.plus(
+ ordersWithoutFee[1].takerAssetAmount.dividedToIntegerBy(2),
+ );
+
+ 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 primaryTakerAssetFillAmount = ethValue;
+ const totalEthSpent = primaryTakerAssetFillAmount.plus(gasPrice.times(tx.gasUsed));
+
+ expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
+ expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
+ );
+ expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFillAmount),
+ );
+ 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);
+ });
+ it('should buy the exact amount of makerAsset and return excess ETH', async () => {
+ const ordersWithoutFee = [orderWithoutFee];
+ const feeOrders: SignedOrder[] = [];
const makerAssetFillAmount = orderWithoutFee.makerAssetAmount.dividedToIntegerBy(2);
const ethValue = orderWithoutFee.takerAssetAmount;
@@ -477,11 +630,11 @@ describe(ContractName.Forwarder, () => {
);
expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
});
- it('should buy the exact amount of assets with fee abstraction', async () => {
- ordersWithFee = [orderWithFee];
- feeOrders = [feeOrder];
+ it('should buy the exact amount of makerAsset and pay ZRX from feeOrders', async () => {
+ const ordersWithFee = [orderWithFee];
+ const feeOrders = [feeOrder];
const makerAssetFillAmount = orderWithFee.makerAssetAmount.dividedToIntegerBy(2);
- const ethValue = orderWithoutFee.takerAssetAmount;
+ const ethValue = orderWithFee.takerAssetAmount;
tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(ordersWithFee, feeOrders, makerAssetFillAmount, {
value: ethValue,
@@ -514,13 +667,13 @@ describe(ContractName.Forwarder, () => {
);
expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
});
- it('should buy the exact amount of assets when buying ZRX with fee abstraction', async () => {
+ it('should buy slightly greater than makerAssetAmount when buying ZRX', async () => {
orderWithFee = await orderFactory.newSignedOrderAsync({
makerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address),
takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT),
});
- ordersWithFee = [orderWithFee];
- feeOrders = [];
+ const ordersWithFee = [orderWithFee];
+ const feeOrders: SignedOrder[] = [];
const makerAssetFillAmount = orderWithFee.makerAssetAmount.dividedToIntegerBy(2);
const ethValue = orderWithFee.takerAssetAmount;
tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(ordersWithFee, feeOrders, makerAssetFillAmount, {
@@ -545,13 +698,20 @@ describe(ContractName.Forwarder, () => {
const makerFeePaid = orderWithFee.makerFee
.times(primaryTakerAssetFillAmount)
.dividedToIntegerBy(orderWithFee.takerAssetAmount);
+ const totalZrxPurchased = makerAssetFilledAmount.minus(takerFeePaid);
+ // Up to 1 wei worth of ZRX will be overbought per order
+ const maxOverboughtZrx = new BigNumber(1)
+ .times(orderWithFee.makerAssetAmount)
+ .dividedToIntegerBy(orderWithFee.takerAssetAmount);
+ expect(totalZrxPurchased).to.be.bignumber.gte(makerAssetFillAmount);
+ expect(totalZrxPurchased).to.be.bignumber.lte(makerAssetFillAmount.plus(maxOverboughtZrx));
expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal(
erc20Balances[makerAddress][zrxToken.address].minus(makerAssetFilledAmount).minus(makerFeePaid),
);
expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal(
- erc20Balances[takerAddress][zrxToken.address].plus(makerAssetFilledAmount).minus(takerFeePaid),
+ erc20Balances[takerAddress][zrxToken.address].plus(totalZrxPurchased),
);
expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount),
@@ -562,373 +722,282 @@ describe(ContractName.Forwarder, () => {
);
expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
});
- // it('throws if fees are higher than 5% when buying zrx', async () => {
- // const highFeeZRXOrder = orderFactory.newSignedOrder({
- // makerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address),
- // makerAssetAmount: orderWithoutFee.makerAssetAmount,
- // takerFee: orderWithoutFee.makerAssetAmount.times(0.06),
- // });
- // ordersWithFee = [highFeeZRXOrder];
- // feeOrders = [];
- // const makerAssetAmount = orderWithoutFee.makerAssetAmount.div(2);
- // const fillAmountWei = await forwarderWrapper.calculateMarketBuyFillAmountWeiAsync(
- // ordersWithFee,
- // feeOrders,
- // feePercentage,
- // makerAssetAmount,
- // );
- // return expectTransactionFailedAsync(
- // forwarderWrapper.marketBuyOrdersWithEthAsync(ordersWithFee, feeOrders, makerAssetAmount, {
- // from: takerAddress,
- // value: fillAmountWei,
- // }),
- // RevertReason.UnacceptableThreshold,
- // );
- // });
- // it('throws if fees are higher than 5% when buying erc20', async () => {
- // const highFeeERC20Order = orderFactory.newSignedOrder({
- // takerFee: orderWithoutFee.makerAssetAmount.times(0.06),
- // });
- // ordersWithFee = [highFeeERC20Order];
- // feeOrders = [feeOrder];
- // const makerAssetAmount = orderWithoutFee.makerAssetAmount.div(2);
- // const fillAmountWei = await forwarderWrapper.calculateMarketBuyFillAmountWeiAsync(
- // ordersWithFee,
- // feeOrders,
- // feePercentage,
- // makerAssetAmount,
- // );
- // return expectTransactionFailedAsync(
- // forwarderWrapper.marketBuyOrdersWithEthAsync(ordersWithFee, feeOrders, makerAssetAmount, {
- // from: takerAddress,
- // value: fillAmountWei,
- // }),
- // RevertReason.UnacceptableThreshold as any,
- // );
- // });
- // it('throws if makerAssetAmount is 0', async () => {
- // const makerAssetAmount = new BigNumber(0);
- // const fillAmountWei = await forwarderWrapper.calculateMarketBuyFillAmountWeiAsync(
- // ordersWithFee,
- // feeOrders,
- // feePercentage,
- // makerAssetAmount,
- // );
- // return expectTransactionFailedAsync(
- // forwarderWrapper.marketBuyOrdersWithEthAsync(ordersWithFee, feeOrders, makerAssetAmount, {
- // from: takerAddress,
- // value: fillAmountWei,
- // }),
- // RevertReason.ValueGreaterThanZero as any,
- // );
- // });
- // it('throws if the amount of ETH sent in is less than the takerAssetFilledAmount', async () => {
- // const makerAssetAmount = orderWithoutFee.makerAssetAmount;
- // const primaryTakerAssetFillAmount = orderWithoutFee.takerAssetAmount.div(2);
- // const zero = new BigNumber(0);
- // // Deposit enough taker balance to fill the order
- // const wethDepositTxHash = await wethContract.deposit.sendTransactionAsync({
- // from: takerAddress,
- // value: orderWithoutFee.takerAssetAmount,
- // });
- // await web3Wrapper.awaitTransactionSuccessAsync(wethDepositTxHash);
- // // Transfer all of this WETH to the forwarding contract
- // const wethTransferTxHash = await wethContract.transfer.sendTransactionAsync(
- // forwarderContract.address,
- // orderWithoutFee.takerAssetAmount,
- // { from: takerAddress },
- // );
- // await web3Wrapper.awaitTransactionSuccessAsync(wethTransferTxHash);
- // // We use the contract directly to get around wrapper validations and calculations
- // const formattedOrders = formatters.createMarketSellOrders(signedOrders, zero);
- // const formattedFeeOrders = formatters.createMarketSellOrders(feeOrders, zero);
- // return expectTransactionFailedAsync(
- // forwarderContract.marketBuyOrdersWithEth.sendTransactionAsync(
- // formattedOrders.orders,
- // formattedOrders.signatures,
- // formattedFeeOrders.orders,
- // formattedFeeOrders.signatures,
- // makerAssetAmount,
- // zero,
- // constants.NULL_ADDRESS,
- // { value: primaryTakerAssetFillAmount, from: takerAddress },
- // ),
- // RevertReason.InvalidMsgValue,
- // );
- // });
+ it('should not change balances 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);
+ });
+ it('should buy an ERC721 asset from a single order', async () => {
+ const makerAssetId = erc721MakerAssetIds[0];
+ orderWithoutFee = await orderFactory.newSignedOrderAsync({
+ makerAssetAmount: new BigNumber(1),
+ makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId),
+ });
+ const ordersWithoutFee = [orderWithoutFee];
+ const feeOrders: SignedOrder[] = [];
+ const makerAssetFillAmount = new BigNumber(1);
+ 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);
+ });
+ it('should buy an ERC721 asset and ignore later orders with different makerAssetData', async () => {
+ const makerAssetId = erc721MakerAssetIds[0];
+ orderWithoutFee = await orderFactory.newSignedOrderAsync({
+ makerAssetAmount: new BigNumber(1),
+ makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId),
+ });
+ const differentMakerAssetDataOrder = await orderFactory.newSignedOrderAsync();
+ const ordersWithoutFee = [orderWithoutFee, differentMakerAssetDataOrder];
+ 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],
+ );
+ });
+ it('should buy an ERC721 asset and pay ZRX fees from a single fee order', async () => {
+ const makerAssetId = erc721MakerAssetIds[0];
+ orderWithFee = await orderFactory.newSignedOrderAsync({
+ makerAssetAmount: new BigNumber(1),
+ makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId),
+ takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT),
+ });
+ const ordersWithFee = [orderWithFee];
+ const feeOrders = [feeOrder];
+ const makerAssetFillAmount = orderWithFee.makerAssetAmount;
+ const primaryTakerAssetFillAmount = orderWithFee.takerAssetAmount;
+ const feeAmount = orderWithFee.takerFee;
+ const wethSpentOnFeeOrders = ForwarderWrapper.getWethForFeeOrders(feeAmount, feeOrders);
+ const ethValue = primaryTakerAssetFillAmount.plus(wethSpentOnFeeOrders);
+
+ tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(ordersWithFee, feeOrders, makerAssetFillAmount, {
+ value: ethValue,
+ from: takerAddress,
+ });
+ 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 totalEthSpent = ethValue.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).plus(wethSpentOnFeeOrders),
+ );
+ 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);
+ });
+ it('should buy an ERC721 asset and pay ZRX fees from multiple fee orders', async () => {
+ const makerAssetId = erc721MakerAssetIds[0];
+ orderWithFee = await orderFactory.newSignedOrderAsync({
+ makerAssetAmount: new BigNumber(1),
+ makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId),
+ takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT),
+ });
+ const ordersWithFee = [orderWithFee];
+ const makerAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address);
+ const makerAssetAmount = orderWithFee.takerFee.dividedToIntegerBy(2);
+ const takerAssetAmount = feeOrder.takerAssetAmount
+ .times(makerAssetAmount)
+ .dividedToIntegerBy(feeOrder.makerAssetAmount);
+
+ const firstFeeOrder = await orderFactory.newSignedOrderAsync({
+ makerAssetData,
+ makerAssetAmount,
+ takerAssetAmount,
+ });
+ const secondFeeOrder = await orderFactory.newSignedOrderAsync({
+ makerAssetData,
+ makerAssetAmount,
+ takerAssetAmount,
+ });
+ const feeOrders = [firstFeeOrder, secondFeeOrder];
+
+ const makerAssetFillAmount = orderWithFee.makerAssetAmount;
+ const primaryTakerAssetFillAmount = orderWithFee.takerAssetAmount;
+ const feeAmount = orderWithFee.takerFee;
+ const wethSpentOnFeeOrders = ForwarderWrapper.getWethForFeeOrders(feeAmount, feeOrders);
+ const ethValue = primaryTakerAssetFillAmount.plus(wethSpentOnFeeOrders);
+
+ tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(ordersWithFee, feeOrders, makerAssetFillAmount, {
+ value: ethValue,
+ from: takerAddress,
+ });
+ 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 totalEthSpent = ethValue.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).plus(wethSpentOnFeeOrders),
+ );
+ 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);
+ });
+ });
+ describe('marketBuyOrdersWithEth with extra fees', () => {
+ it('should buy an asset and send fee to feeRecipient', async () => {
+ const ordersWithoutFee = [orderWithoutFee];
+ const feeOrders: SignedOrder[] = [];
+ const makerAssetFillAmount = orderWithoutFee.makerAssetAmount.dividedToIntegerBy(2);
+ const ethValue = orderWithoutFee.takerAssetAmount;
+
+ const baseFeePercentage = 2;
+ feePercentage = ForwarderWrapper.getPercentageOfValue(constants.PERCENTAGE_DENOMINATOR, baseFeePercentage);
+ const feeRecipientEthBalanceBefore = await web3Wrapper.getBalanceInWeiAsync(feeRecipientAddress);
+ tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(
+ ordersWithoutFee,
+ feeOrders,
+ makerAssetFillAmount,
+ {
+ value: ethValue,
+ from: takerAddress,
+ },
+ { feePercentage, feeRecipient: feeRecipientAddress },
+ );
+ const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
+ const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
+ const feeRecipientEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(feeRecipientAddress);
+ const newBalances = await erc20Wrapper.getBalancesAsync();
+
+ const primaryTakerAssetFillAmount = orderWithoutFee.takerAssetAmount.dividedToIntegerBy(2);
+ const ethSpentOnFee = ForwarderWrapper.getPercentageOfValue(primaryTakerAssetFillAmount, baseFeePercentage);
+ const totalEthSpent = primaryTakerAssetFillAmount.plus(ethSpentOnFee).plus(gasPrice.times(tx.gasUsed));
+
+ expect(feeRecipientEthBalanceAfter).to.be.bignumber.equal(feeRecipientEthBalanceBefore.plus(ethSpentOnFee));
+ expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
+ expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount),
+ );
+ expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFillAmount),
+ );
+ 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);
+ });
+ it('should fail if the fee is set too high', async () => {
+ const ordersWithoutFee = [orderWithoutFee];
+ const feeOrders: SignedOrder[] = [];
+ const makerAssetFillAmount = orderWithoutFee.makerAssetAmount.dividedToIntegerBy(2);
+ const ethValue = orderWithoutFee.takerAssetAmount;
+
+ const baseFeePercentage = 6;
+ feePercentage = ForwarderWrapper.getPercentageOfValue(constants.PERCENTAGE_DENOMINATOR, baseFeePercentage);
+ await expectTransactionFailedAsync(
+ forwarderWrapper.marketBuyOrdersWithEthAsync(
+ ordersWithoutFee,
+ feeOrders,
+ makerAssetFillAmount,
+ {
+ value: ethValue,
+ from: takerAddress,
+ },
+ { feePercentage, feeRecipient: feeRecipientAddress },
+ ),
+ RevertReason.FeePercentageTooLarge,
+ );
+ });
+ it('should fail if there is not enough ETH remaining to pay the fee', async () => {
+ const ordersWithoutFee = [orderWithoutFee];
+ const feeOrders: SignedOrder[] = [];
+ const makerAssetFillAmount = orderWithoutFee.makerAssetAmount.dividedToIntegerBy(2);
+ const ethValue = orderWithoutFee.takerAssetAmount.dividedToIntegerBy(2);
+
+ const baseFeePercentage = 2;
+ feePercentage = ForwarderWrapper.getPercentageOfValue(constants.PERCENTAGE_DENOMINATOR, baseFeePercentage);
+ await expectTransactionFailedAsync(
+ forwarderWrapper.marketBuyOrdersWithEthAsync(
+ ordersWithoutFee,
+ feeOrders,
+ makerAssetFillAmount,
+ {
+ value: ethValue,
+ from: takerAddress,
+ },
+ { feePercentage, feeRecipient: feeRecipientAddress },
+ ),
+ RevertReason.InsufficientEthRemaining,
+ );
+ });
});
- // describe('marketBuyOrdersWithEth - ERC721', async () => {
- // it('buys ERC721 assets', async () => {
- // const makerAssetId = erc721MakerAssetIds[0];
- // orderWithoutFee = orderFactory.newSignedOrder({
- // makerAssetAmount: new BigNumber(1),
- // makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId),
- // });
- // feeOrders = [];
- // signedOrders = [orderWithoutFee];
- // const makerAssetAmount = new BigNumber(signedOrders.length);
- // const fillAmountWei = await forwarderWrapper.calculateMarketBuyFillAmountWeiAsync(
- // signedOrders,
- // feeOrders,
- // feePercentage,
- // makerAssetAmount,
- // );
- // tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(signedOrders, feeOrders, makerAssetAmount, {
- // from: takerAddress,
- // value: fillAmountWei,
- // });
- // const newOwnerTakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId);
- // expect(newOwnerTakerAsset).to.be.bignumber.equal(takerAddress);
- // });
- // it('buys ERC721 assets with fee abstraction', async () => {
- // const makerAssetId = erc721MakerAssetIds[0];
- // orderWithoutFee = orderFactory.newSignedOrder({
- // makerAssetAmount: new BigNumber(1),
- // takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT),
- // makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId),
- // });
- // signedOrders = [orderWithoutFee];
- // const makerAssetAmount = new BigNumber(signedOrders.length);
- // const fillAmountWei = await forwarderWrapper.calculateMarketBuyFillAmountWeiAsync(
- // signedOrders,
- // feeOrders,
- // feePercentage,
- // makerAssetAmount,
- // );
- // tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(signedOrders, feeOrders, makerAssetAmount, {
- // from: takerAddress,
- // value: fillAmountWei,
- // });
- // const newOwnerTakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId);
- // expect(newOwnerTakerAsset).to.be.bignumber.equal(takerAddress);
- // });
- // it('buys ERC721 assets with fee abstraction and pays fee to fee recipient', async () => {
- // const makerAssetId = erc721MakerAssetIds[0];
- // orderWithoutFee = orderFactory.newSignedOrder({
- // makerAssetAmount: new BigNumber(1),
- // takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT),
- // makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId),
- // });
- // signedOrders = [orderWithoutFee];
- // feePercentage = 100;
- // const initTakerBalanceWei = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
- // const initFeeRecipientBalanceWei = await web3Wrapper.getBalanceInWeiAsync(feeRecipientAddress);
- // const makerAssetAmount = new BigNumber(signedOrders.length);
- // const fillAmountWei = await forwarderWrapper.calculateMarketBuyFillAmountWeiAsync(
- // signedOrders,
- // feeOrders,
- // feePercentage,
- // makerAssetAmount,
- // );
- // tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(
- // signedOrders,
- // feeOrders,
- // makerAssetAmount,
- // {
- // from: takerAddress,
- // value: fillAmountWei,
- // gasPrice: gasPrice,
- // },
- // {
- // feePercentage,
- // feeRecipient: feeRecipientAddress,
- // },
- // );
- // const afterFeeRecipientEthBalance = await web3Wrapper.getBalanceInWeiAsync(feeRecipientAddress);
- // const afterTakerBalanceWei = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
- // const takerFilledAmount = initTakerBalanceWei.minus(afterTakerBalanceWei).plus(tx.gasUsed);
- // const newOwnerTakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId);
- // expect(newOwnerTakerAsset).to.be.bignumber.equal(takerAddress);
- // const balanceDiff = afterFeeRecipientEthBalance.minus(initFeeRecipientBalanceWei);
- // expect(takerFilledAmount.dividedToIntegerBy(balanceDiff)).to.be.bignumber.equal(101);
- // expect(takerFilledAmount.minus(balanceDiff).dividedToIntegerBy(balanceDiff)).to.be.bignumber.equal(100);
- // });
- // it('buys multiple ERC721 assets with fee abstraction and pays fee to fee recipient', async () => {
- // const makerAssetId1 = erc721MakerAssetIds[0];
- // const makerAssetId2 = erc721MakerAssetIds[1];
- // const signedOrder1 = orderFactory.newSignedOrder({
- // makerAssetAmount: new BigNumber(1),
- // takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(3), DECIMALS_DEFAULT),
- // makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId1),
- // });
- // const signedOrder2 = orderFactory.newSignedOrder({
- // makerAssetAmount: new BigNumber(1),
- // takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(4), DECIMALS_DEFAULT),
- // makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId2),
- // });
- // signedOrders = [signedOrder1, signedOrder2];
- // feePercentage = 10;
- // const makerAssetAmount = new BigNumber(signedOrders.length);
- // const fillAmountWei = await forwarderWrapper.calculateMarketBuyFillAmountWeiAsync(
- // signedOrders,
- // feeOrders,
- // feePercentage,
- // makerAssetAmount,
- // );
- // tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(signedOrders, feeOrders, makerAssetAmount, {
- // from: takerAddress,
- // value: fillAmountWei,
- // });
- // const newOwnerTakerAsset1 = await erc721Token.ownerOf.callAsync(makerAssetId1);
- // expect(newOwnerTakerAsset1).to.be.bignumber.equal(takerAddress);
- // const newOwnerTakerAsset2 = await erc721Token.ownerOf.callAsync(makerAssetId2);
- // expect(newOwnerTakerAsset2).to.be.bignumber.equal(takerAddress);
- // });
- // it('buys ERC721 assets with fee abstraction and handles fee orders filled and excess eth', async () => {
- // const makerAssetId = erc721MakerAssetIds[0];
- // feePercentage = 0;
- // // In this scenario a total of 6 ZRX fees need to be paid.
- // // There are two fee orders, but the first fee order is partially filled while
- // // the Forwarding contract tx is in the mempool.
- // const erc721MakerAssetAmount = new BigNumber(1);
- // orderWithoutFee = orderFactory.newSignedOrder({
- // makerAssetAmount: erc721MakerAssetAmount,
- // takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), DECIMALS_DEFAULT),
- // takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(6), DECIMALS_DEFAULT),
- // makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId),
- // });
- // signedOrders = [orderWithoutFee];
- // const firstFeeOrder = orderFactory.newSignedOrder({
- // makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(8), DECIMALS_DEFAULT),
- // takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(0.1), DECIMALS_DEFAULT),
- // makerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address),
- // takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(0), DECIMALS_DEFAULT),
- // });
- // const secondFeeOrder = orderFactory.newSignedOrder({
- // makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(8), DECIMALS_DEFAULT),
- // takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(0.12), DECIMALS_DEFAULT),
- // makerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address),
- // takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(0), DECIMALS_DEFAULT),
- // });
- // feeOrders = [firstFeeOrder, secondFeeOrder];
- // const makerAssetAmount = new BigNumber(signedOrders.length);
- // const fillAmountWei = await forwarderWrapper.calculateMarketBuyFillAmountWeiAsync(
- // signedOrders,
- // feeOrders,
- // feePercentage,
- // erc721MakerAssetAmount,
- // );
- // // Simulate another otherAddress user partially filling firstFeeOrder
- // const firstFeeOrderFillAmount = firstFeeOrder.makerAssetAmount.div(2);
- // tx = await forwarderWrapper.marketBuyOrdersWithEthAsync([firstFeeOrder], [], firstFeeOrderFillAmount, {
- // from: otherAddress,
- // value: fillAmountWei,
- // });
- // // For tests we calculate how much this should've cost given that firstFeeOrder was filled
- // const expectedFillAmountWei = await forwarderWrapper.calculateMarketBuyFillAmountWeiAsync(
- // signedOrders,
- // feeOrders,
- // feePercentage,
- // erc721MakerAssetAmount,
- // );
- // // With 4 ZRX remaining in firstFeeOrder, the secondFeeOrder will need to be filled to make up
- // // the total amount of fees required (6)
- // // Since the fee orders can be filled while the transaction is pending the user safely sends in
- // // extra ether to cover any slippage
- // const initEthBalance = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
- // const slippageFillAmountWei = fillAmountWei.times(2);
- // tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(signedOrders, feeOrders, makerAssetAmount, {
- // from: takerAddress,
- // value: slippageFillAmountWei,
- // gasPrice: gasPrice,
- // });
- // const afterEthBalance = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
- // const expectedEthBalanceAfterGasCosts = initEthBalance.minus(expectedFillAmountWei).minus(tx.gasUsed);
- // const newOwnerTakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId);
- // expect(newOwnerTakerAsset).to.be.bignumber.equal(takerAddress);
- // expect(afterEthBalance).to.be.bignumber.equal(expectedEthBalanceAfterGasCosts);
- // });
- // it('buys ERC721 assets with fee abstraction and handles fee orders filled', async () => {
- // const makerAssetId = erc721MakerAssetIds[0];
- // feePercentage = 0;
- // // In this scenario a total of 6 ZRX fees need to be paid.
- // // There are two fee orders, but the first fee order is partially filled while
- // // the Forwarding contract tx is in the mempool.
- // const erc721MakerAssetAmount = new BigNumber(1);
- // orderWithoutFee = orderFactory.newSignedOrder({
- // makerAssetAmount: erc721MakerAssetAmount,
- // takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), DECIMALS_DEFAULT),
- // takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(6), DECIMALS_DEFAULT),
- // makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId),
- // });
- // const zrxMakerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(8), DECIMALS_DEFAULT);
- // signedOrders = [orderWithoutFee];
- // const firstFeeOrder = orderFactory.newSignedOrder({
- // makerAssetAmount: zrxMakerAssetAmount,
- // takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(0.1), DECIMALS_DEFAULT),
- // makerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address),
- // takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(0), DECIMALS_DEFAULT),
- // });
- // const secondFeeOrder = orderFactory.newSignedOrder({
- // makerAssetAmount: zrxMakerAssetAmount,
- // takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(0.12), DECIMALS_DEFAULT),
- // makerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address),
- // takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(0), DECIMALS_DEFAULT),
- // });
- // feeOrders = [firstFeeOrder, secondFeeOrder];
- // const makerAssetAmount = new BigNumber(signedOrders.length);
- // const fillAmountWei = await forwarderWrapper.calculateMarketBuyFillAmountWeiAsync(
- // signedOrders,
- // feeOrders,
- // feePercentage,
- // erc721MakerAssetAmount,
- // );
- // // Simulate another otherAddress user partially filling firstFeeOrder
- // const firstFeeOrderFillAmount = firstFeeOrder.makerAssetAmount.div(2);
- // tx = await forwarderWrapper.marketBuyOrdersWithEthAsync([firstFeeOrder], [], firstFeeOrderFillAmount, {
- // from: otherAddress,
- // value: fillAmountWei,
- // });
- // const expectedFillAmountWei = await forwarderWrapper.calculateMarketBuyFillAmountWeiAsync(
- // signedOrders,
- // feeOrders,
- // feePercentage,
- // erc721MakerAssetAmount,
- // );
- // tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(signedOrders, feeOrders, makerAssetAmount, {
- // from: takerAddress,
- // value: expectedFillAmountWei,
- // });
- // const newOwnerTakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId);
- // expect(newOwnerTakerAsset).to.be.bignumber.equal(takerAddress);
- // });
- // it('throws when mixed ERC721 and ERC20 assets', async () => {
- // const makerAssetId = erc721MakerAssetIds[0];
- // const erc721SignedOrder = orderFactory.newSignedOrder({
- // makerAssetAmount: new BigNumber(1),
- // makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId),
- // });
- // const erc20SignedOrder = orderFactory.newSignedOrder();
- // signedOrders = [erc721SignedOrder, erc20SignedOrder];
- // const makerAssetAmount = new BigNumber(signedOrders.length);
- // const fillAmountWei = erc20SignedOrder.takerAssetAmount.plus(erc721SignedOrder.takerAssetAmount);
- // return expectTransactionFailedAsync(
- // forwarderWrapper.marketBuyOrdersWithEthAsync(signedOrders, feeOrders, makerAssetAmount, {
- // from: takerAddress,
- // value: fillAmountWei,
- // }),
- // RevertReason.LibBytesGreaterOrEqualTo32LengthRequired,
- // );
- // });
- // it('throws when mixed ERC721 and ERC20 assets with ERC20 first', async () => {
- // const makerAssetId = erc721MakerAssetIds[0];
- // const erc721SignedOrder = orderFactory.newSignedOrder({
- // makerAssetAmount: new BigNumber(1),
- // makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId),
- // });
- // const erc20SignedOrder = orderFactory.newSignedOrder();
- // signedOrders = [erc20SignedOrder, erc721SignedOrder];
- // const makerAssetAmount = new BigNumber(signedOrders.length);
- // const fillAmountWei = erc20SignedOrder.takerAssetAmount.plus(erc721SignedOrder.takerAssetAmount);
- // return expectTransactionFailedAsync(
- // forwarderWrapper.marketBuyOrdersWithEthAsync(signedOrders, feeOrders, makerAssetAmount, {
- // from: takerAddress,
- // value: fillAmountWei,
- // }),
- // RevertReason.InvalidTakerAmount,
- // );
- // });
- // });
});
// tslint:disable:max-file-line-count
// tslint:enable:no-unnecessary-type-assertion
diff --git a/packages/contracts/test/utils/forwarder_wrapper.ts b/packages/contracts/test/utils/forwarder_wrapper.ts
index 773ddf897..ef7476e36 100644
--- a/packages/contracts/test/utils/forwarder_wrapper.ts
+++ b/packages/contracts/test/utils/forwarder_wrapper.ts
@@ -25,13 +25,13 @@ export class ForwarderWrapper {
let remainingFeeAmount = feeAmount;
_.forEach(feeOrders, feeOrder => {
const feeAvailable = feeOrder.makerAssetAmount.minus(feeOrder.takerFee);
- if (!remainingFeeAmount.isZero() && feeAvailable.gte(remainingFeeAmount)) {
+ if (!remainingFeeAmount.isZero() && feeAvailable.gt(remainingFeeAmount)) {
wethAmount = wethAmount
.plus(feeOrder.takerAssetAmount.times(remainingFeeAmount).dividedToIntegerBy(feeAvailable))
.plus(1);
remainingFeeAmount = new BigNumber(0);
} else if (!remainingFeeAmount.isZero()) {
- wethAmount = wethAmount.plus(feeOrder.takerAssetAmount).plus(1);
+ wethAmount = wethAmount.plus(feeOrder.takerAssetAmount);
remainingFeeAmount = remainingFeeAmount.minus(feeAvailable);
}
});
diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts
index b96bdb182..555cd7dec 100644
--- a/packages/types/src/index.ts
+++ b/packages/types/src/index.ts
@@ -215,10 +215,10 @@ export enum RevertReason {
LibBytesGreaterOrEqualToSourceBytesLengthRequired = 'GREATER_OR_EQUAL_TO_SOURCE_BYTES_LENGTH_REQUIRED',
Erc20InsufficientBalance = 'ERC20_INSUFFICIENT_BALANCE',
Erc20InsufficientAllowance = 'ERC20_INSUFFICIENT_ALLOWANCE',
- UnacceptableThreshold = 'UNACCEPTABLE_THRESHOLD',
FeePercentageTooLarge = 'FEE_PERCENTAGE_TOO_LARGE',
ValueGreaterThanZero = 'VALUE_GREATER_THAN_ZERO',
InvalidMsgValue = 'INVALID_MSG_VALUE',
+ InsufficientEthRemaining = 'INSUFFICIENT_ETH_REMAINING',
}
export enum StatusCodes {