aboutsummaryrefslogtreecommitdiffstats
path: root/packages/contracts/test/forwarder/forwarder.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/contracts/test/forwarder/forwarder.ts')
-rw-r--r--packages/contracts/test/forwarder/forwarder.ts1265
1 files changed, 725 insertions, 540 deletions
diff --git a/packages/contracts/test/forwarder/forwarder.ts b/packages/contracts/test/forwarder/forwarder.ts
index 0256d7d81..19639d3aa 100644
--- a/packages/contracts/test/forwarder/forwarder.ts
+++ b/packages/contracts/test/forwarder/forwarder.ts
@@ -18,7 +18,6 @@ import { constants } from '../utils/constants';
import { ERC20Wrapper } from '../utils/erc20_wrapper';
import { ERC721Wrapper } from '../utils/erc721_wrapper';
import { ExchangeWrapper } from '../utils/exchange_wrapper';
-import { formatters } from '../utils/formatters';
import { ForwarderWrapper } from '../utils/forwarder_wrapper';
import { OrderFactory } from '../utils/order_factory';
import { ContractName, ERC20BalancesByOwner } from '../utils/types';
@@ -28,8 +27,7 @@ chaiSetup.configure();
const expect = chai.expect;
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
const DECIMALS_DEFAULT = 18;
-// Set a gasPrice so when checking balance of msg.sender we can accurately calculate gasPrice*gasUsed
-const DEFAULT_GAS_PRICE = new BigNumber(1);
+const MAX_WETH_FILL_PERCENTAGE = 95;
describe(ContractName.Forwarder, () => {
let makerAddress: string;
@@ -47,25 +45,28 @@ describe(ContractName.Forwarder, () => {
let forwarderWrapper: ForwarderWrapper;
let exchangeWrapper: ExchangeWrapper;
- let signedOrder: SignedOrder;
- let signedOrders: SignedOrder[];
+ let orderWithoutFee: SignedOrder;
let orderWithFee: SignedOrder;
- let signedOrdersWithFee: SignedOrder[];
let feeOrder: SignedOrder;
- let feeOrders: SignedOrder[];
let orderFactory: OrderFactory;
let erc20Wrapper: ERC20Wrapper;
let erc20Balances: ERC20BalancesByOwner;
let tx: TransactionReceiptWithDecodedLogs;
let erc721MakerAssetIds: BigNumber[];
- let feeProportion: number = 0;
+ let takerEthBalanceBefore: BigNumber;
+ let feePercentage: BigNumber;
+ let gasPrice: BigNumber;
before(async () => {
await blockchainLifecycle.startAsync();
const accounts = await web3Wrapper.getAvailableAddressesAsync();
const usedAddresses = ([owner, makerAddress, takerAddress, feeRecipientAddress, otherAddress] = accounts);
+ const txHash = await web3Wrapper.sendTransactionAsync({ from: accounts[0], to: accounts[0], value: 0 });
+ const transaction = await web3Wrapper.getTransactionByHashAsync(txHash);
+ gasPrice = new BigNumber(transaction.gasPrice);
+
const erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner);
erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner);
@@ -135,681 +136,865 @@ describe(ContractName.Forwarder, () => {
wethAssetData,
);
forwarderContract = new ForwarderContract(forwarderInstance.abi, forwarderInstance.address, provider);
- forwarderWrapper = new ForwarderWrapper(forwarderContract, provider, zrxToken.address);
+ forwarderWrapper = new ForwarderWrapper(forwarderContract, provider);
+ const zrxDepositAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(10000), 18);
+ await web3Wrapper.awaitTransactionSuccessAsync(
+ await zrxToken.transfer.sendTransactionAsync(forwarderContract.address, zrxDepositAmount),
+ );
erc20Wrapper.addTokenOwnerAddress(forwarderInstance.address);
-
- web3Wrapper.abiDecoder.addABI(forwarderContract.abi);
- web3Wrapper.abiDecoder.addABI(exchangeInstance.abi);
});
after(async () => {
await blockchainLifecycle.revertAsync();
});
beforeEach(async () => {
await blockchainLifecycle.startAsync();
- feeProportion = 0;
erc20Balances = await erc20Wrapper.getBalancesAsync();
- signedOrder = await orderFactory.newSignedOrderAsync();
- signedOrders = [signedOrder];
+ takerEthBalanceBefore = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
+ orderWithoutFee = await orderFactory.newSignedOrderAsync();
feeOrder = await orderFactory.newSignedOrderAsync({
makerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address),
takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT),
});
- feeOrders = [feeOrder];
orderWithFee = await orderFactory.newSignedOrderAsync({
takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT),
});
- signedOrdersWithFee = [orderWithFee];
});
afterEach(async () => {
await blockchainLifecycle.revertAsync();
});
- describe('calculations', () => {
- it('throws if partially filled orders passed in are not enough to satisfy requested amount', async () => {
- feeOrders = [feeOrder];
- const makerTokenFillAmount = feeOrder.makerAssetAmount.div(2);
- const fillAmountWei = await forwarderWrapper.calculateMarketBuyFillAmountWeiAsync(
- feeOrders,
- [],
- feeProportion,
- makerTokenFillAmount,
- );
- // Fill the feeOrder
- tx = await forwarderWrapper.marketBuyTokensWithEthAsync(feeOrders, [], makerTokenFillAmount, {
+
+ describe('marketSellOrdersWithEth without extra fees', () => {
+ 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, {
+ value: ethValue,
from: takerAddress,
- value: fillAmountWei,
});
- return expect(
- forwarderWrapper.calculateMarketBuyFillAmountWeiAsync(
- feeOrders,
- [],
- feeProportion,
- makerTokenFillAmount,
- ),
- ).to.be.rejectedWith('Unable to satisfy makerAssetFillAmount with provided orders');
+ 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 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('throws if orders passed are cancelled', async () => {
- tx = await exchangeWrapper.cancelOrderAsync(feeOrder, makerAddress);
- // Cancel the feeOrder
- return expect(
- forwarderWrapper.calculateMarketBuyFillAmountWeiAsync(
- feeOrders,
- [],
- feeProportion,
- feeOrder.makerAssetAmount.div(2),
- ),
- ).to.be.rejectedWith('Unable to satisfy makerAssetFillAmount with provided orders');
+ 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);
});
- });
- describe('marketSellEthForERC20 without extra fees', () => {
- it('should fill the order', async () => {
- const fillAmount = signedOrder.takerAssetAmount.div(2);
- const makerBalanceBefore = erc20Balances[makerAddress][defaultMakerAssetAddress];
- const takerBalanceBefore = erc20Balances[takerAddress][defaultMakerAssetAddress];
- feeOrders = [];
- tx = await forwarderWrapper.marketSellEthForERC20Async(signedOrders, feeOrders, {
- value: fillAmount,
+ 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, {
+ value: ethValue,
from: takerAddress,
});
+ const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
+ const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
const newBalances = await erc20Wrapper.getBalancesAsync();
- const makerBalanceAfter = newBalances[makerAddress][defaultMakerAssetAddress];
- const takerBalanceAfter = newBalances[takerAddress][defaultMakerAssetAddress];
- const makerTokenFillAmount = fillAmount
- .times(signedOrder.makerAssetAmount)
- .dividedToIntegerBy(signedOrder.takerAssetAmount);
-
- expect(makerBalanceAfter).to.be.bignumber.equal(makerBalanceBefore.minus(makerTokenFillAmount));
- expect(takerBalanceAfter).to.be.bignumber.equal(takerBalanceBefore.plus(makerTokenFillAmount));
- expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(new BigNumber(0));
+
+ const primaryTakerAssetFillAmount = ForwarderWrapper.getPercentageOfValue(
+ ethValue,
+ MAX_WETH_FILL_PERCENTAGE,
+ );
+ const makerAssetFillAmount = primaryTakerAssetFillAmount
+ .times(orderWithoutFee.makerAssetAmount)
+ .dividedToIntegerBy(orderWithoutFee.takerAssetAmount);
+ const feeAmount = ForwarderWrapper.getPercentageOfValue(
+ orderWithFee.takerFee.dividedToIntegerBy(2),
+ 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 and perform fee abstraction', async () => {
- const fillAmount = signedOrder.takerAssetAmount.div(4);
- const takerBalanceBefore = erc20Balances[takerAddress][defaultMakerAssetAddress];
- tx = await forwarderWrapper.marketSellEthForERC20Async(signedOrdersWithFee, feeOrders, {
- value: fillAmount,
+ 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 takerBalanceAfter = newBalances[takerAddress][defaultMakerAssetAddress];
- const acceptPercentage = 98;
- const acceptableThreshold = takerBalanceBefore.plus(fillAmount.times(acceptPercentage).dividedBy(100));
- const isWithinThreshold = takerBalanceAfter.greaterThanOrEqualTo(acceptableThreshold);
- expect(isWithinThreshold).to.be.true();
- expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(new BigNumber(0));
+ 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),
});
- signedOrdersWithFee = [orderWithFee];
- feeOrders = [];
- const fillAmount = signedOrder.takerAssetAmount.div(4);
- const takerBalanceBefore = erc20Balances[takerAddress][zrxToken.address];
- tx = await forwarderWrapper.marketSellEthForERC20Async(signedOrdersWithFee, feeOrders, {
- value: fillAmount,
+ const ordersWithFee = [orderWithFee];
+ const feeOrders: SignedOrder[] = [];
+ const ethValue = orderWithFee.takerAssetAmount.dividedToIntegerBy(2);
+
+ 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 takerBalanceAfter = newBalances[takerAddress][zrxToken.address];
- const acceptPercentage = 98;
- const acceptableThreshold = takerBalanceBefore.plus(fillAmount.times(acceptPercentage).dividedBy(100));
- const isWithinThreshold = takerBalanceAfter.greaterThanOrEqualTo(acceptableThreshold);
- expect(isWithinThreshold).to.be.true();
- expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(new BigNumber(0));
+ const makerAssetFillAmount = orderWithFee.makerAssetAmount.dividedToIntegerBy(2);
+ const totalEthSpent = ethValue.plus(gasPrice.times(tx.gasUsed));
+ const takerFeePaid = orderWithFee.takerFee.dividedToIntegerBy(2);
+ const makerFeePaid = orderWithFee.makerFee.dividedToIntegerBy(2);
+
+ expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
+ expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][zrxToken.address].minus(makerAssetFillAmount).minus(makerFeePaid),
+ );
+ expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][zrxToken.address].plus(makerAssetFillAmount).minus(takerFeePaid),
+ );
+ expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal(
+ erc20Balances[makerAddress][weth.address].plus(ethValue),
+ );
+ expect(newBalances[forwarderContract.address][weth.address]).to.be.bignumber.equal(constants.ZERO_AMOUNT);
+ expect(newBalances[forwarderContract.address][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[forwarderContract.address][zrxToken.address],
+ );
+ expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
});
- it('should fail if sent an ETH amount too high', async () => {
- signedOrder = await orderFactory.newSignedOrderAsync({
- makerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address),
- takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT),
+ it('should refund remaining ETH if amount is greater than takerAssetAmount', async () => {
+ const ordersWithoutFee = [orderWithoutFee];
+ const feeOrders: SignedOrder[] = [];
+ const ethValue = orderWithoutFee.takerAssetAmount.times(2);
+
+ tx = await forwarderWrapper.marketSellOrdersWithEthAsync(ordersWithoutFee, feeOrders, {
+ value: ethValue,
+ from: takerAddress,
});
- const fillAmount = signedOrder.takerAssetAmount.times(2);
- return expectTransactionFailedAsync(
- forwarderWrapper.marketSellEthForERC20Async(signedOrdersWithFee, feeOrders, {
- value: fillAmount,
- from: takerAddress,
- }),
- RevertReason.UnacceptableThreshold,
- );
+ const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
+ const totalEthSpent = orderWithoutFee.takerAssetAmount.plus(gasPrice.times(tx.gasUsed));
+
+ expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
});
- it('should fail if fee abstraction amount is too high', async () => {
+ it('should revert if ZRX cannot be fully repurchased', async () => {
orderWithFee = await orderFactory.newSignedOrderAsync({
takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(50), DECIMALS_DEFAULT),
});
- signedOrdersWithFee = [orderWithFee];
+ const ordersWithFee = [orderWithFee];
feeOrder = await orderFactory.newSignedOrderAsync({
makerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address),
makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT),
});
- feeOrders = [feeOrder];
- const fillAmount = signedOrder.takerAssetAmount.div(4);
+ const feeOrders = [feeOrder];
+ const ethValue = orderWithFee.takerAssetAmount;
return expectTransactionFailedAsync(
- forwarderWrapper.marketSellEthForERC20Async(signedOrdersWithFee, feeOrders, {
- value: fillAmount,
+ forwarderWrapper.marketSellOrdersWithEthAsync(ordersWithFee, feeOrders, {
+ value: ethValue,
from: takerAddress,
}),
- RevertReason.TransferFailed,
+ RevertReason.CompleteFillFailed,
);
});
- it('throws when mixed ERC721 and ERC20 assets with ERC20 first', async () => {
+ it('should not fill orders with different makerAssetData than the first order', async () => {
const makerAssetId = erc721MakerAssetIds[0];
const erc721SignedOrder = await orderFactory.newSignedOrderAsync({
makerAssetAmount: new BigNumber(1),
makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId),
});
const erc20SignedOrder = await orderFactory.newSignedOrderAsync();
- signedOrders = [erc20SignedOrder, erc721SignedOrder];
- const fillAmountWei = erc20SignedOrder.takerAssetAmount.plus(erc721SignedOrder.takerAssetAmount);
- return expectTransactionFailedAsync(
- forwarderWrapper.marketSellEthForERC20Async(signedOrders, feeOrders, {
- from: takerAddress,
- value: fillAmountWei,
- }),
- RevertReason.InvalidOrderSignature,
- );
+ const ordersWithoutFee = [erc20SignedOrder, erc721SignedOrder];
+ const feeOrders: SignedOrder[] = [];
+ const ethValue = erc20SignedOrder.takerAssetAmount.plus(erc721SignedOrder.takerAssetAmount);
+
+ tx = await forwarderWrapper.marketSellOrdersWithEthAsync(ordersWithoutFee, feeOrders, {
+ value: ethValue,
+ from: takerAddress,
+ });
+ const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
+ const totalEthSpent = erc20SignedOrder.takerAssetAmount.plus(gasPrice.times(tx.gasUsed));
+
+ expect(takerEthBalanceAfter).to.be.bignumber.equal(takerEthBalanceBefore.minus(totalEthSpent));
});
});
- describe('marketSellEthForERC20 with extra fees', () => {
- it('should fill the order and send fee to fee recipient', async () => {
- const initEthBalance = await web3Wrapper.getBalanceInWeiAsync(feeRecipientAddress);
- const fillAmount = signedOrder.takerAssetAmount.div(2);
- feeProportion = 150; // 1.5%
- feeOrders = [];
- tx = await forwarderWrapper.marketSellEthForERC20Async(
- signedOrders,
+ describe('marketSellOrdersWithEth with extra fees', () => {
+ it('should fill the order and send fee to feeRecipient', async () => {
+ const ordersWithoutFee = [orderWithoutFee];
+ const feeOrders: SignedOrder[] = [];
+ const ethValue = orderWithoutFee.takerAssetAmount.div(2);
+
+ const baseFeePercentage = 2;
+ feePercentage = ForwarderWrapper.getPercentageOfValue(constants.PERCENTAGE_DENOMINATOR, baseFeePercentage);
+ const feeRecipientEthBalanceBefore = await web3Wrapper.getBalanceInWeiAsync(feeRecipientAddress);
+ tx = await forwarderWrapper.marketSellOrdersWithEthAsync(
+ ordersWithoutFee,
feeOrders,
{
+ value: ethValue,
from: takerAddress,
- value: fillAmount,
- gasPrice: DEFAULT_GAS_PRICE,
- },
- {
- feeProportion,
- feeRecipient: feeRecipientAddress,
},
+ { feePercentage, feeRecipient: feeRecipientAddress },
);
+ const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
+ const feeRecipientEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(feeRecipientAddress);
+ const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
const newBalances = await erc20Wrapper.getBalancesAsync();
- const makerBalanceBefore = erc20Balances[makerAddress][defaultMakerAssetAddress];
- const makerBalanceAfter = newBalances[makerAddress][defaultMakerAssetAddress];
- const takerBalanceAfter = newBalances[takerAddress][defaultMakerAssetAddress];
- const afterEthBalance = await web3Wrapper.getBalanceInWeiAsync(feeRecipientAddress);
- const takerBoughtAmount = takerBalanceAfter.minus(erc20Balances[takerAddress][defaultMakerAssetAddress]);
- expect(makerBalanceAfter).to.be.bignumber.equal(makerBalanceBefore.minus(takerBoughtAmount));
- expect(afterEthBalance).to.be.bignumber.equal(
- initEthBalance.plus(fillAmount.times(feeProportion).dividedBy(10000)),
+ const primaryTakerAssetFillAmount = ForwarderWrapper.getPercentageOfValue(
+ ethValue,
+ MAX_WETH_FILL_PERCENTAGE,
+ );
+ const makerAssetFillAmount = primaryTakerAssetFillAmount
+ .times(orderWithoutFee.makerAssetAmount)
+ .dividedToIntegerBy(orderWithoutFee.takerAssetAmount);
+ const ethSpentOnFee = ForwarderWrapper.getPercentageOfValue(primaryTakerAssetFillAmount, baseFeePercentage);
+ const totalEthSpent = primaryTakerAssetFillAmount.plus(ethSpentOnFee).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[forwarderContract.address][weth.address]).to.be.bignumber.equal(new BigNumber(0));
+ 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(feeRecipientEthBalanceAfter).to.be.bignumber.equal(feeRecipientEthBalanceBefore.plus(ethSpentOnFee));
+ expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
});
it('should fail if the fee is set too high', async () => {
- const initEthBalance = await web3Wrapper.getBalanceInWeiAsync(feeRecipientAddress);
- const fillAmount = signedOrder.takerAssetAmount.div(2);
- feeProportion = 1500; // 15.0%
- feeOrders = [];
+ const ethValue = orderWithoutFee.takerAssetAmount.div(2);
+ const baseFeePercentage = 6;
+ feePercentage = ForwarderWrapper.getPercentageOfValue(ethValue, baseFeePercentage);
+ const ordersWithoutFee = [orderWithoutFee];
+ const feeOrders: SignedOrder[] = [];
await expectTransactionFailedAsync(
- forwarderWrapper.marketSellEthForERC20Async(
- signedOrders,
+ forwarderWrapper.marketSellOrdersWithEthAsync(
+ ordersWithoutFee,
feeOrders,
- { from: takerAddress, value: fillAmount, gasPrice: DEFAULT_GAS_PRICE },
- { feeProportion, feeRecipient: feeRecipientAddress },
+ { from: takerAddress, value: ethValue, gasPrice },
+ { feePercentage, feeRecipient: feeRecipientAddress },
),
- RevertReason.FeeProportionTooLarge,
+ RevertReason.FeePercentageTooLarge,
+ );
+ });
+ 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,
);
- const afterEthBalance = await web3Wrapper.getBalanceInWeiAsync(feeRecipientAddress);
- expect(afterEthBalance).to.be.bignumber.equal(initEthBalance);
});
});
- describe('marketBuyTokensWithEth', () => {
- it('should buy the exact amount of assets', async () => {
- const makerAssetAmount = signedOrder.makerAssetAmount.div(2);
- const initEthBalance = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
- const balancesBefore = await erc20Wrapper.getBalancesAsync();
- const rate = signedOrder.makerAssetAmount.dividedBy(signedOrder.takerAssetAmount);
- const fillAmountWei = makerAssetAmount.dividedToIntegerBy(rate);
- feeOrders = [];
- tx = await forwarderWrapper.marketBuyTokensWithEthAsync(signedOrders, feeOrders, makerAssetAmount, {
+ describe('marketBuyOrdersWithEth without extra fees', () => {
+ 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);
+
+ tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(ordersWithoutFee, feeOrders, makerAssetFillAmount, {
+ value: ethValue,
from: takerAddress,
- value: fillAmountWei,
- gasPrice: DEFAULT_GAS_PRICE,
});
+ const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
+ const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
const newBalances = await erc20Wrapper.getBalancesAsync();
- const takerBalanceBefore = balancesBefore[takerAddress][defaultMakerAssetAddress];
- const takerBalanceAfter = newBalances[takerAddress][defaultMakerAssetAddress];
- const afterEthBalance = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
- const expectedEthBalanceAfterGasCosts = initEthBalance.minus(fillAmountWei).minus(tx.gasUsed);
- expect(takerBalanceAfter).to.be.bignumber.eq(takerBalanceBefore.plus(makerAssetAmount));
- expect(afterEthBalance).to.be.bignumber.eq(expectedEthBalanceAfterGasCosts);
+
+ 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 assets and return excess ETH', async () => {
- const makerAssetAmount = signedOrder.makerAssetAmount.div(2);
- const initEthBalance = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
- const balancesBefore = await erc20Wrapper.getBalancesAsync();
- const rate = signedOrder.makerAssetAmount.dividedBy(signedOrder.takerAssetAmount);
- const fillAmount = makerAssetAmount.dividedToIntegerBy(rate);
- const excessFillAmount = fillAmount.times(2);
- feeOrders = [];
- tx = await forwarderWrapper.marketBuyTokensWithEthAsync(signedOrders, feeOrders, makerAssetAmount, {
+ 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,
- value: excessFillAmount,
- gasPrice: DEFAULT_GAS_PRICE,
});
+ const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
+ const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
const newBalances = await erc20Wrapper.getBalancesAsync();
- const takerBalanceBefore = balancesBefore[takerAddress][defaultMakerAssetAddress];
- const takerBalanceAfter = newBalances[takerAddress][defaultMakerAssetAddress];
- const afterEthBalance = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
- const expectedEthBalanceAfterGasCosts = initEthBalance.minus(fillAmount).minus(tx.gasUsed);
- expect(takerBalanceAfter).to.be.bignumber.eq(takerBalanceBefore.plus(makerAssetAmount));
- expect(afterEthBalance).to.be.bignumber.eq(expectedEthBalanceAfterGasCosts);
+
+ 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 assets with fee abstraction', async () => {
- const makerAssetAmount = signedOrder.makerAssetAmount.div(2);
- const balancesBefore = await erc20Wrapper.getBalancesAsync();
- const rate = signedOrder.makerAssetAmount.dividedBy(signedOrder.takerAssetAmount);
- const fillAmount = makerAssetAmount.dividedToIntegerBy(rate);
- const excessFillAmount = fillAmount.times(2);
- tx = await forwarderWrapper.marketBuyTokensWithEthAsync(signedOrdersWithFee, feeOrders, makerAssetAmount, {
+ 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;
+
+ tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(ordersWithoutFee, feeOrders, makerAssetFillAmount, {
+ value: ethValue,
from: takerAddress,
- value: excessFillAmount,
});
+ const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
+ const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
const newBalances = await erc20Wrapper.getBalancesAsync();
- const takerBalanceBefore = balancesBefore[takerAddress][defaultMakerAssetAddress];
- const takerBalanceAfter = newBalances[takerAddress][defaultMakerAssetAddress];
- expect(takerBalanceAfter).to.be.bignumber.eq(takerBalanceBefore.plus(makerAssetAmount));
- });
- it('should buy the exact amount of assets when buying zrx with fee abstraction', async () => {
- signedOrder = await orderFactory.newSignedOrderAsync({
- makerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address),
- takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT),
- });
- signedOrdersWithFee = [signedOrder];
- feeOrders = [];
- const makerAssetAmount = signedOrder.makerAssetAmount.div(2);
- const takerWeiBalanceBefore = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
- const balancesBefore = await erc20Wrapper.getBalancesAsync();
- const fillAmountWei = await forwarderWrapper.calculateMarketBuyFillAmountWeiAsync(
- signedOrdersWithFee,
- feeOrders,
- feeProportion,
- makerAssetAmount,
+
+ const primaryTakerAssetFillAmount = ethValue.dividedToIntegerBy(2);
+ 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),
);
- tx = await forwarderWrapper.marketBuyTokensWithEthAsync(signedOrdersWithFee, feeOrders, makerAssetAmount, {
+ 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 pay ZRX from feeOrders', async () => {
+ const ordersWithFee = [orderWithFee];
+ const feeOrders = [feeOrder];
+ const makerAssetFillAmount = orderWithFee.makerAssetAmount.dividedToIntegerBy(2);
+ const ethValue = orderWithFee.takerAssetAmount;
+
+ tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(ordersWithFee, feeOrders, makerAssetFillAmount, {
+ value: ethValue,
from: takerAddress,
- value: fillAmountWei,
- gasPrice: DEFAULT_GAS_PRICE,
});
+ const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
+ const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address);
const newBalances = await erc20Wrapper.getBalancesAsync();
- const takerTokenBalanceBefore = balancesBefore[takerAddress][zrxToken.address];
- const takerTokenBalanceAfter = newBalances[takerAddress][zrxToken.address];
- const takerWeiBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress);
- const expectedCostAfterGas = fillAmountWei.plus(tx.gasUsed);
- expect(takerTokenBalanceAfter).to.be.bignumber.greaterThan(takerTokenBalanceBefore.plus(makerAssetAmount));
- expect(takerWeiBalanceAfter).to.be.bignumber.equal(takerWeiBalanceBefore.minus(expectedCostAfterGas));
- });
- it('throws if fees are higher than 5% when buying zrx', async () => {
- const highFeeZRXOrder = await orderFactory.newSignedOrderAsync({
- makerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address),
- makerAssetAmount: signedOrder.makerAssetAmount,
- takerFee: signedOrder.makerAssetAmount.times(0.06),
- });
- signedOrdersWithFee = [highFeeZRXOrder];
- feeOrders = [];
- const makerAssetAmount = signedOrder.makerAssetAmount.div(2);
- const fillAmountWei = await forwarderWrapper.calculateMarketBuyFillAmountWeiAsync(
- signedOrdersWithFee,
- feeOrders,
- feeProportion,
- makerAssetAmount,
+
+ const primaryTakerAssetFillAmount = orderWithFee.takerAssetAmount.dividedToIntegerBy(2);
+ const feeAmount = orderWithFee.takerFee.dividedToIntegerBy(2);
+ 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),
);
- return expectTransactionFailedAsync(
- forwarderWrapper.marketBuyTokensWithEthAsync(signedOrdersWithFee, feeOrders, makerAssetAmount, {
- from: takerAddress,
- value: fillAmountWei,
- }),
- RevertReason.UnacceptableThreshold,
+ 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('throws if fees are higher than 5% when buying erc20', async () => {
- const highFeeERC20Order = await orderFactory.newSignedOrderAsync({
- takerFee: signedOrder.makerAssetAmount.times(0.06),
+ 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),
});
- signedOrdersWithFee = [highFeeERC20Order];
- feeOrders = [feeOrder];
- const makerAssetAmount = signedOrder.makerAssetAmount.div(2);
- const fillAmountWei = await forwarderWrapper.calculateMarketBuyFillAmountWeiAsync(
- signedOrdersWithFee,
- feeOrders,
- feeProportion,
- makerAssetAmount,
+ const ordersWithFee = [orderWithFee];
+ const feeOrders: SignedOrder[] = [];
+ const makerAssetFillAmount = orderWithFee.makerAssetAmount.dividedToIntegerBy(2);
+ const ethValue = orderWithFee.takerAssetAmount;
+ 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 newBalances = await erc20Wrapper.getBalancesAsync();
+
+ const primaryTakerAssetFillAmount = ForwarderWrapper.getWethForFeeOrders(
+ makerAssetFillAmount,
+ ordersWithFee,
);
- return expectTransactionFailedAsync(
- forwarderWrapper.marketBuyTokensWithEthAsync(signedOrdersWithFee, feeOrders, makerAssetAmount, {
- from: takerAddress,
- value: fillAmountWei,
- }),
- RevertReason.UnacceptableThreshold as any,
+ const totalEthSpent = primaryTakerAssetFillAmount.plus(gasPrice.times(tx.gasUsed));
+ const makerAssetFilledAmount = orderWithFee.makerAssetAmount
+ .times(primaryTakerAssetFillAmount)
+ .dividedToIntegerBy(orderWithFee.takerAssetAmount);
+ const takerFeePaid = orderWithFee.takerFee
+ .times(primaryTakerAssetFillAmount)
+ .dividedToIntegerBy(orderWithFee.takerAssetAmount);
+ 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),
);
- });
- it('throws if makerAssetAmount is 0', async () => {
- const makerAssetAmount = new BigNumber(0);
- const fillAmountWei = await forwarderWrapper.calculateMarketBuyFillAmountWeiAsync(
- signedOrdersWithFee,
- feeOrders,
- feeProportion,
- makerAssetAmount,
+ expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[takerAddress][zrxToken.address].plus(totalZrxPurchased),
);
- return expectTransactionFailedAsync(
- forwarderWrapper.marketBuyTokensWithEthAsync(signedOrdersWithFee, feeOrders, makerAssetAmount, {
- from: takerAddress,
- value: fillAmountWei,
- }),
- RevertReason.ValueGreaterThanZero as any,
+ 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][zrxToken.address]).to.be.bignumber.equal(
+ erc20Balances[forwarderContract.address][zrxToken.address],
);
+ expect(forwarderEthBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT);
});
- it('throws if the amount of ETH sent in is less than the takerAssetFilledAmount', async () => {
- const makerAssetAmount = signedOrder.makerAssetAmount;
- const fillAmount = signedOrder.takerAssetAmount.div(2);
- const zero = new BigNumber(0);
- // Deposit enough taker balance to fill the order
- const wethDepositTxHash = await wethContract.deposit.sendTransactionAsync({
+ 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,
- value: signedOrder.takerAssetAmount,
});
- await web3Wrapper.awaitTransactionSuccessAsync(wethDepositTxHash);
- // Transfer all of this WETH to the forwarding contract
- const wethTransferTxHash = await wethContract.transfer.sendTransactionAsync(
- forwarderContract.address,
- signedOrder.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.marketBuyTokensWithEth.sendTransactionAsync(
- formattedOrders.orders,
- formattedOrders.signatures,
- formattedFeeOrders.orders,
- formattedFeeOrders.signatures,
- makerAssetAmount,
- zero,
- constants.NULL_ADDRESS,
- { value: fillAmount, from: takerAddress },
- ),
- RevertReason.InvalidMsgValue,
- );
+ 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);
});
- });
- describe('marketBuyTokensWithEth - ERC721', async () => {
- it('buys ERC721 assets', async () => {
+ it('should buy an ERC721 asset from a single order', async () => {
const makerAssetId = erc721MakerAssetIds[0];
- signedOrder = await orderFactory.newSignedOrderAsync({
+ orderWithoutFee = await orderFactory.newSignedOrderAsync({
makerAssetAmount: new BigNumber(1),
makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId),
});
- feeOrders = [];
- signedOrders = [signedOrder];
- const makerAssetAmount = new BigNumber(signedOrders.length);
- const fillAmountWei = await forwarderWrapper.calculateMarketBuyFillAmountWeiAsync(
- signedOrders,
- feeOrders,
- feeProportion,
- makerAssetAmount,
- );
- tx = await forwarderWrapper.marketBuyTokensWithEthAsync(signedOrders, feeOrders, makerAssetAmount, {
+ 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: fillAmountWei,
+ value: ethValue,
});
- const newOwnerTakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId);
- expect(newOwnerTakerAsset).to.be.bignumber.equal(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 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('buys ERC721 assets with fee abstraction', async () => {
+ it('should buy an ERC721 asset and ignore later orders with different makerAssetData', async () => {
const makerAssetId = erc721MakerAssetIds[0];
- signedOrder = await orderFactory.newSignedOrderAsync({
+ orderWithoutFee = await orderFactory.newSignedOrderAsync({
makerAssetAmount: new BigNumber(1),
- takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT),
makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId),
});
- signedOrders = [signedOrder];
- const makerAssetAmount = new BigNumber(signedOrders.length);
- const fillAmountWei = await forwarderWrapper.calculateMarketBuyFillAmountWeiAsync(
- signedOrders,
- feeOrders,
- feeProportion,
- makerAssetAmount,
- );
- tx = await forwarderWrapper.marketBuyTokensWithEthAsync(signedOrders, feeOrders, makerAssetAmount, {
+ 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: fillAmountWei,
+ value: ethValue,
});
- 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];
- signedOrder = await orderFactory.newSignedOrderAsync({
- makerAssetAmount: new BigNumber(1),
- takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT),
- makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId),
- });
- signedOrders = [signedOrder];
- feeProportion = 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,
- feeProportion,
- makerAssetAmount,
+ 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),
);
- tx = await forwarderWrapper.marketBuyTokensWithEthAsync(
- signedOrders,
- feeOrders,
- makerAssetAmount,
- {
- from: takerAddress,
- value: fillAmountWei,
- gasPrice: DEFAULT_GAS_PRICE,
- },
- {
- feeProportion,
- feeRecipient: feeRecipientAddress,
- },
+ 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,
);
- 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 = await orderFactory.newSignedOrderAsync({
- makerAssetAmount: new BigNumber(1),
- takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(3), DECIMALS_DEFAULT),
- makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId1),
- });
- const signedOrder2 = await orderFactory.newSignedOrderAsync({
- makerAssetAmount: new BigNumber(1),
- takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(4), DECIMALS_DEFAULT),
- makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId2),
- });
- signedOrders = [signedOrder1, signedOrder2];
- feeProportion = 10;
- const makerAssetAmount = new BigNumber(signedOrders.length);
- const fillAmountWei = await forwarderWrapper.calculateMarketBuyFillAmountWeiAsync(
- signedOrders,
- feeOrders,
- feeProportion,
- makerAssetAmount,
+ 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],
);
- tx = await forwarderWrapper.marketBuyTokensWithEthAsync(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 () => {
+ it('should buy an ERC721 asset and pay ZRX fees from a single fee order', async () => {
const makerAssetId = erc721MakerAssetIds[0];
- feeProportion = 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);
- signedOrder = await orderFactory.newSignedOrderAsync({
- makerAssetAmount: erc721MakerAssetAmount,
- takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), DECIMALS_DEFAULT),
- takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(6), DECIMALS_DEFAULT),
+ orderWithFee = await orderFactory.newSignedOrderAsync({
+ makerAssetAmount: new BigNumber(1),
makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId),
+ takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT),
});
- signedOrders = [signedOrder];
- const firstFeeOrder = await orderFactory.newSignedOrderAsync({
- 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 = await orderFactory.newSignedOrderAsync({
- 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,
- feeProportion,
- erc721MakerAssetAmount,
- );
- // Simulate another otherAddress user partially filling firstFeeOrder
- const firstFeeOrderFillAmount = firstFeeOrder.makerAssetAmount.div(2);
- tx = await forwarderWrapper.marketBuyTokensWithEthAsync([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,
- feeProportion,
- 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.marketBuyTokensWithEthAsync(signedOrders, feeOrders, makerAssetAmount, {
+ 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,
- value: slippageFillAmountWei,
- gasPrice: DEFAULT_GAS_PRICE,
});
- 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);
+ 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('buys ERC721 assets with fee abstraction and handles fee orders filled', async () => {
+ it('should buy an ERC721 asset and pay ZRX fees from multiple fee orders', async () => {
const makerAssetId = erc721MakerAssetIds[0];
- feeProportion = 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);
- signedOrder = await orderFactory.newSignedOrderAsync({
- makerAssetAmount: erc721MakerAssetAmount,
- takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), DECIMALS_DEFAULT),
- takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(6), DECIMALS_DEFAULT),
+ orderWithFee = await orderFactory.newSignedOrderAsync({
+ makerAssetAmount: new BigNumber(1),
makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId),
+ takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT),
});
- const zrxMakerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(8), DECIMALS_DEFAULT);
- signedOrders = [signedOrder];
+ 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({
- makerAssetAmount: zrxMakerAssetAmount,
- takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(0.1), DECIMALS_DEFAULT),
- makerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address),
- takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(0), DECIMALS_DEFAULT),
+ makerAssetData,
+ makerAssetAmount,
+ takerAssetAmount,
});
const secondFeeOrder = await orderFactory.newSignedOrderAsync({
- 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,
- feeProportion,
- erc721MakerAssetAmount,
- );
- // Simulate another otherAddress user partially filling firstFeeOrder
- const firstFeeOrderFillAmount = firstFeeOrder.makerAssetAmount.div(2);
- tx = await forwarderWrapper.marketBuyTokensWithEthAsync([firstFeeOrder], [], firstFeeOrderFillAmount, {
- from: otherAddress,
- value: fillAmountWei,
+ makerAssetData,
+ makerAssetAmount,
+ takerAssetAmount,
});
- const expectedFillAmountWei = await forwarderWrapper.calculateMarketBuyFillAmountWeiAsync(
- signedOrders,
- feeOrders,
- feeProportion,
- erc721MakerAssetAmount,
- );
- tx = await forwarderWrapper.marketBuyTokensWithEthAsync(signedOrders, feeOrders, makerAssetAmount, {
+ 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,
- value: expectedFillAmountWei,
});
- const newOwnerTakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId);
- expect(newOwnerTakerAsset).to.be.bignumber.equal(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('throws when mixed ERC721 and ERC20 assets', async () => {
- const makerAssetId = erc721MakerAssetIds[0];
- const erc721SignedOrder = await orderFactory.newSignedOrderAsync({
- makerAssetAmount: new BigNumber(1),
- makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId),
- });
- const erc20SignedOrder = await orderFactory.newSignedOrderAsync();
- signedOrders = [erc721SignedOrder, erc20SignedOrder];
- const makerAssetAmount = new BigNumber(signedOrders.length);
- const fillAmountWei = erc20SignedOrder.takerAssetAmount.plus(erc721SignedOrder.takerAssetAmount);
- return expectTransactionFailedAsync(
- forwarderWrapper.marketBuyTokensWithEthAsync(signedOrders, feeOrders, makerAssetAmount, {
+ });
+ 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,
- value: fillAmountWei,
- }),
- RevertReason.LibBytesGreaterOrEqualTo32LengthRequired,
+ },
+ { 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('throws when mixed ERC721 and ERC20 assets with ERC20 first', async () => {
- const makerAssetId = erc721MakerAssetIds[0];
- const erc721SignedOrder = await orderFactory.newSignedOrderAsync({
- makerAssetAmount: new BigNumber(1),
- makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId),
- });
- const erc20SignedOrder = await orderFactory.newSignedOrderAsync();
- signedOrders = [erc20SignedOrder, erc721SignedOrder];
- const makerAssetAmount = new BigNumber(signedOrders.length);
- const fillAmountWei = erc20SignedOrder.takerAssetAmount.plus(erc721SignedOrder.takerAssetAmount);
- return expectTransactionFailedAsync(
- forwarderWrapper.marketBuyTokensWithEthAsync(signedOrders, feeOrders, makerAssetAmount, {
- from: takerAddress,
- value: fillAmountWei,
- }),
- RevertReason.InvalidTakerAmount,
+ 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,
);
});
});