From 1f0e819756ea247e592a8a05c53561894c8ca87a Mon Sep 17 00:00:00 2001 From: Amir Bandeali Date: Tue, 17 Jul 2018 11:33:51 -0700 Subject: Fix minimal tests --- packages/contracts/test/forwarder/forwarder.ts | 1248 +++++++++++--------- packages/contracts/test/utils/constants.ts | 2 + packages/contracts/test/utils/forwarder_wrapper.ts | 226 +--- packages/types/src/index.ts | 2 +- 4 files changed, 748 insertions(+), 730 deletions(-) diff --git a/packages/contracts/test/forwarder/forwarder.ts b/packages/contracts/test/forwarder/forwarder.ts index 0256d7d81..f2966fe75 100644 --- a/packages/contracts/test/forwarder/forwarder.ts +++ b/packages/contracts/test/forwarder/forwarder.ts @@ -47,10 +47,10 @@ describe(ContractName.Forwarder, () => { let forwarderWrapper: ForwarderWrapper; let exchangeWrapper: ExchangeWrapper; - let signedOrder: SignedOrder; - let signedOrders: SignedOrder[]; + let orderWithoutFee: SignedOrder; + let ordersWithoutFee: SignedOrder[]; let orderWithFee: SignedOrder; - let signedOrdersWithFee: SignedOrder[]; + let ordersWithFee: SignedOrder[]; let feeOrder: SignedOrder; let feeOrders: SignedOrder[]; let orderFactory: OrderFactory; @@ -59,7 +59,9 @@ describe(ContractName.Forwarder, () => { let tx: TransactionReceiptWithDecodedLogs; let erc721MakerAssetIds: BigNumber[]; - let feeProportion: number = 0; + let takerEthBalanceBefore: BigNumber; + let feePercentage: BigNumber; + const MAX_WETH_FILL_PERCENTAGE = 95; before(async () => { await blockchainLifecycle.startAsync(); @@ -135,7 +137,11 @@ 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); @@ -146,673 +152,785 @@ describe(ContractName.Forwarder, () => { }); 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, { - from: takerAddress, - value: fillAmountWei, - }); - return expect( - forwarderWrapper.calculateMarketBuyFillAmountWeiAsync( - feeOrders, - [], - feeProportion, - makerTokenFillAmount, - ), - ).to.be.rejectedWith('Unable to satisfy makerAssetFillAmount with provided orders'); - }); - 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'); - }); - }); - describe('marketSellEthForERC20 without extra fees', () => { + + describe('marketSellOrdersWithEth 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]; + ordersWithoutFee = [orderWithoutFee]; feeOrders = []; - tx = await forwarderWrapper.marketSellEthForERC20Async(signedOrders, feeOrders, { - value: fillAmount, + const ethValue = orderWithoutFee.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 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 totalEthSpent = primaryTakerAssetFillAmount.plus(DEFAULT_GAS_PRICE.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 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 order and pay ZRX fees from feeOrders', async () => { + ordersWithFee = [orderWithFee]; + 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 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.dividedToIntegerBy(2), + MAX_WETH_FILL_PERCENTAGE, + ); + const wethSpentOnFeeOrders = ForwarderWrapper.getWethForFeeOrders(feeAmount, feeOrders); + const totalEthSpent = primaryTakerAssetFillAmount + .plus(wethSpentOnFeeOrders) + .plus(DEFAULT_GAS_PRICE.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]; + ordersWithFee = [orderWithFee]; feeOrders = []; - const fillAmount = signedOrder.takerAssetAmount.div(4); - const takerBalanceBefore = erc20Balances[takerAddress][zrxToken.address]; - tx = await forwarderWrapper.marketSellEthForERC20Async(signedOrdersWithFee, feeOrders, { - value: fillAmount, + 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(DEFAULT_GAS_PRICE.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 () => { + ordersWithoutFee = [orderWithoutFee]; + 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(DEFAULT_GAS_PRICE.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]; + 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 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, - ); + ordersWithoutFee = [erc20SignedOrder, erc721SignedOrder]; + 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(DEFAULT_GAS_PRICE.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% + describe('marketSellOrdersWithEth with extra fees', () => { + it('should fill the order and send fee to feeRecipient', async () => { + ordersWithoutFee = [orderWithoutFee]; feeOrders = []; - tx = await forwarderWrapper.marketSellEthForERC20Async( - signedOrders, + 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(DEFAULT_GAS_PRICE.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% + const feeRecipientEthBalanceBefore = await web3Wrapper.getBalanceInWeiAsync(feeRecipientAddress); + const ethValue = orderWithoutFee.takerAssetAmount.div(2); + const baseFeePercentage = 6; + feePercentage = ForwarderWrapper.getPercentageOfValue(ethValue, baseFeePercentage); feeOrders = []; 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: DEFAULT_GAS_PRICE }, + { feePercentage, feeRecipient: feeRecipientAddress }, ), - RevertReason.FeeProportionTooLarge, + RevertReason.FeePercentageTooLarge, ); - const afterEthBalance = await web3Wrapper.getBalanceInWeiAsync(feeRecipientAddress); - expect(afterEthBalance).to.be.bignumber.equal(initEthBalance); + const feeRecipientEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(feeRecipientAddress); + expect(feeRecipientEthBalanceAfter).to.be.bignumber.equal(feeRecipientEthBalanceBefore); }); }); - describe('marketBuyTokensWithEth', () => { + describe('marketBuyOrdersWithEth without extra fees', () => { 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); + ordersWithoutFee = [orderWithoutFee]; feeOrders = []; - tx = await forwarderWrapper.marketBuyTokensWithEthAsync(signedOrders, feeOrders, makerAssetAmount, { + 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(DEFAULT_GAS_PRICE.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, { - from: takerAddress, - value: excessFillAmount, - gasPrice: DEFAULT_GAS_PRICE, - }); - 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); - }); - 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, { - from: takerAddress, - value: excessFillAmount, - }); - 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]; + ordersWithoutFee = [orderWithoutFee]; 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, - ); - tx = await forwarderWrapper.marketBuyTokensWithEthAsync(signedOrdersWithFee, feeOrders, makerAssetAmount, { + const makerAssetFillAmount = orderWithoutFee.makerAssetAmount.dividedToIntegerBy(2); + const ethValue = orderWithoutFee.takerAssetAmount; + + 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 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 = ethValue.dividedToIntegerBy(2); + const totalEthSpent = primaryTakerAssetFillAmount.plus(DEFAULT_GAS_PRICE.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), ); - }); - it('throws if fees are higher than 5% when buying erc20', async () => { - const highFeeERC20Order = await orderFactory.newSignedOrderAsync({ - takerFee: signedOrder.makerAssetAmount.times(0.06), - }); - signedOrdersWithFee = [highFeeERC20Order]; - feeOrders = [feeOrder]; - const makerAssetAmount = signedOrder.makerAssetAmount.div(2); - const fillAmountWei = await forwarderWrapper.calculateMarketBuyFillAmountWeiAsync( - signedOrdersWithFee, - feeOrders, - feeProportion, - makerAssetAmount, + expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal( + erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount), ); - return expectTransactionFailedAsync( - forwarderWrapper.marketBuyTokensWithEthAsync(signedOrdersWithFee, feeOrders, makerAssetAmount, { - from: takerAddress, - value: fillAmountWei, - }), - RevertReason.UnacceptableThreshold as any, + 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 makerAssetAmount is 0', async () => { - const makerAssetAmount = new BigNumber(0); - const fillAmountWei = await forwarderWrapper.calculateMarketBuyFillAmountWeiAsync( - signedOrdersWithFee, - feeOrders, - feeProportion, - makerAssetAmount, - ); - return expectTransactionFailedAsync( - forwarderWrapper.marketBuyTokensWithEthAsync(signedOrdersWithFee, 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 = 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 buy the exact amount of assets with fee abstraction', async () => { + ordersWithFee = [orderWithFee]; + feeOrders = [feeOrder]; + const makerAssetFillAmount = orderWithFee.makerAssetAmount.dividedToIntegerBy(2); + const ethValue = orderWithoutFee.takerAssetAmount; + + tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(ordersWithFee, 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 primaryTakerAssetFillAmount = orderWithFee.takerAssetAmount.dividedToIntegerBy(2); + const feeAmount = orderWithFee.takerFee.dividedToIntegerBy(2); + const wethSpentOnFeeOrders = ForwarderWrapper.getWethForFeeOrders(feeAmount, feeOrders); + const totalEthSpent = primaryTakerAssetFillAmount + .plus(wethSpentOnFeeOrders) + .plus(DEFAULT_GAS_PRICE.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), ); - }); - }); - describe('marketBuyTokensWithEth - ERC721', async () => { - it('buys ERC721 assets', async () => { - const makerAssetId = erc721MakerAssetIds[0]; - signedOrder = 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, + expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFillAmount), ); - tx = await forwarderWrapper.marketBuyTokensWithEthAsync(signedOrders, feeOrders, makerAssetAmount, { - from: takerAddress, - value: fillAmountWei, - }); - const newOwnerTakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId); - expect(newOwnerTakerAsset).to.be.bignumber.equal(takerAddress); + 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', async () => { - const makerAssetId = erc721MakerAssetIds[0]; - signedOrder = await orderFactory.newSignedOrderAsync({ - makerAssetAmount: new BigNumber(1), + it('should buy the exact amount of assets when buying ZRX with fee abstraction', async () => { + orderWithFee = await orderFactory.newSignedOrderAsync({ + makerAssetData: assetProxyUtils.encodeERC20AssetData(zrxToken.address), 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, { + ordersWithFee = [orderWithFee]; + feeOrders = []; + const makerAssetFillAmount = orderWithFee.makerAssetAmount.dividedToIntegerBy(2); + const ethValue = orderWithFee.takerAssetAmount; + tx = await forwarderWrapper.marketBuyOrdersWithEthAsync(ordersWithFee, feeOrders, makerAssetFillAmount, { + value: ethValue, 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]; - 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, - ); - tx = await forwarderWrapper.marketBuyTokensWithEthAsync( - signedOrders, - feeOrders, - makerAssetAmount, - { - from: takerAddress, - value: fillAmountWei, - gasPrice: DEFAULT_GAS_PRICE, - }, - { - feeProportion, - feeRecipient: feeRecipientAddress, - }, + const takerEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(takerAddress); + const forwarderEthBalance = await web3Wrapper.getBalanceInWeiAsync(forwarderContract.address); + const newBalances = await erc20Wrapper.getBalancesAsync(); + + const primaryTakerAssetFillAmount = ForwarderWrapper.getWethForFeeOrders( + makerAssetFillAmount, + ordersWithFee, ); - 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, + const totalEthSpent = primaryTakerAssetFillAmount.plus(DEFAULT_GAS_PRICE.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); + + 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), ); - 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 () => { - 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), - makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId), - }); - 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, { - 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); - }); - it('buys ERC721 assets with fee abstraction and handles fee orders filled', 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), - makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId), - }); - const zrxMakerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(8), DECIMALS_DEFAULT); - signedOrders = [signedOrder]; - 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), - }); - 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, - }); - const expectedFillAmountWei = await forwarderWrapper.calculateMarketBuyFillAmountWeiAsync( - signedOrders, - feeOrders, - feeProportion, - erc721MakerAssetAmount, + expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[takerAddress][zrxToken.address].plus(makerAssetFilledAmount).minus(takerFeePaid), ); - tx = await forwarderWrapper.marketBuyTokensWithEthAsync(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 = 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, { - from: takerAddress, - value: fillAmountWei, - }), - RevertReason.LibBytesGreaterOrEqualTo32LengthRequired, + expect(newBalances[makerAddress][weth.address]).to.be.bignumber.equal( + erc20Balances[makerAddress][weth.address].plus(primaryTakerAssetFillAmount), ); - }); - 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, + 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 fees are higher than 5% when buying zrx', async () => { + // const highFeeZRXOrder = orderFactory.newSignedOrder({ + // makerAssetData: assetProxyUtils.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, + // ); + // }); }); + // describe('marketBuyOrdersWithEth - ERC721', async () => { + // it('buys ERC721 assets', async () => { + // const makerAssetId = erc721MakerAssetIds[0]; + // orderWithoutFee = orderFactory.newSignedOrder({ + // makerAssetAmount: new BigNumber(1), + // makerAssetData: assetProxyUtils.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: assetProxyUtils.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: assetProxyUtils.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: DEFAULT_GAS_PRICE, + // }, + // { + // 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: assetProxyUtils.encodeERC721AssetData(erc721Token.address, makerAssetId1), + // }); + // const signedOrder2 = orderFactory.newSignedOrder({ + // makerAssetAmount: new BigNumber(1), + // takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(4), DECIMALS_DEFAULT), + // makerAssetData: assetProxyUtils.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: assetProxyUtils.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: assetProxyUtils.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: assetProxyUtils.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: 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); + // }); + // 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: assetProxyUtils.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: assetProxyUtils.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: assetProxyUtils.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: assetProxyUtils.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: assetProxyUtils.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/constants.ts b/packages/contracts/test/utils/constants.ts index 7dac38f56..65eaee398 100644 --- a/packages/contracts/test/utils/constants.ts +++ b/packages/contracts/test/utils/constants.ts @@ -49,4 +49,6 @@ export const constants = { takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 18), }, WORD_LENGTH: 32, + ZERO_AMOUNT: new BigNumber(0), + PERCENTAGE_DENOMINATOR: new BigNumber(10).pow(18), }; diff --git a/packages/contracts/test/utils/forwarder_wrapper.ts b/packages/contracts/test/utils/forwarder_wrapper.ts index e39df14b1..773ddf897 100644 --- a/packages/contracts/test/utils/forwarder_wrapper.ts +++ b/packages/contracts/test/utils/forwarder_wrapper.ts @@ -1,5 +1,4 @@ -import { assetDataUtils } from '@0xproject/order-utils'; -import { AssetProxyId, SignedOrder } from '@0xproject/types'; +import { SignedOrder } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import { Web3Wrapper } from '@0xproject/web3-wrapper'; import { Provider, TransactionReceiptWithDecodedLogs, TxDataPayable } from 'ethereum-types'; @@ -12,209 +11,108 @@ import { formatters } from './formatters'; import { LogDecoder } from './log_decoder'; import { MarketSellOrders } from './types'; -const DEFAULT_FEE_PROPORTION = 0; -const PERCENTAGE_DENOMINATOR = 10000; -const ZERO_AMOUNT = new BigNumber(0); -const INSUFFICENT_ORDERS_FOR_MAKER_AMOUNT = 'Unable to satisfy makerAssetFillAmount with provided orders'; - export class ForwarderWrapper { private readonly _web3Wrapper: Web3Wrapper; private readonly _forwarderContract: ForwarderContract; private readonly _logDecoder: LogDecoder; - private readonly _zrxAddress: string; - private static _createOptimizedSellOrders(signedOrders: SignedOrder[]): MarketSellOrders { - const marketSellOrders = formatters.createMarketSellOrders(signedOrders, ZERO_AMOUNT); - const assetDataId = assetDataUtils.decodeAssetProxyId(signedOrders[0].makerAssetData); - // Contract will fill this in for us as all of the assetData is assumed to be the same - for (let i = 0; i < signedOrders.length; i++) { - if (i !== 0 && assetDataId === AssetProxyId.ERC20) { - // Forwarding contract will fill this in from the first order - marketSellOrders.orders[i].makerAssetData = constants.NULL_BYTES; + public static getPercentageOfValue(value: BigNumber, percentage: number): BigNumber { + const numerator = constants.PERCENTAGE_DENOMINATOR.times(percentage).dividedToIntegerBy(100); + const newValue = value.times(numerator).dividedToIntegerBy(constants.PERCENTAGE_DENOMINATOR); + return newValue; + } + public static getWethForFeeOrders(feeAmount: BigNumber, feeOrders: SignedOrder[]): BigNumber { + let wethAmount = new BigNumber(0); + let remainingFeeAmount = feeAmount; + _.forEach(feeOrders, feeOrder => { + const feeAvailable = feeOrder.makerAssetAmount.minus(feeOrder.takerFee); + if (!remainingFeeAmount.isZero() && feeAvailable.gte(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); + remainingFeeAmount = remainingFeeAmount.minus(feeAvailable); } - marketSellOrders.orders[i].takerAssetData = constants.NULL_BYTES; - } - return marketSellOrders; + }); + return wethAmount; } - private static _createOptimizedZRXSellOrders(signedOrders: SignedOrder[]): MarketSellOrders { - const marketSellOrders = formatters.createMarketSellOrders(signedOrders, ZERO_AMOUNT); - // Contract will fill this in for us as all of the assetData is assumed to be the same - for (let i = 0; i < signedOrders.length; i++) { - marketSellOrders.orders[i].makerAssetData = constants.NULL_BYTES; - marketSellOrders.orders[i].takerAssetData = constants.NULL_BYTES; - } - return marketSellOrders; + private static _createOptimizedOrders(signedOrders: SignedOrder[]): MarketSellOrders { + _.forEach(signedOrders, (signedOrder, index) => { + signedOrder.takerAssetData = constants.NULL_BYTES; + if (index > 0) { + signedOrder.makerAssetData = constants.NULL_BYTES; + } + }); + const params = formatters.createMarketSellOrders(signedOrders, constants.ZERO_AMOUNT); + return params; } - private static _calculateAdditionalFeeProportionAmount(feeProportion: number, fillAmountWei: BigNumber): BigNumber { - if (feeProportion > 0) { - // Add to the total ETH transaction to ensure all NFTs can be filled after fees - // 150 = 1.5% = 0.015 - const denominator = new BigNumber(1).minus(new BigNumber(feeProportion).dividedBy(PERCENTAGE_DENOMINATOR)); - return fillAmountWei.dividedBy(denominator).round(0, BigNumber.ROUND_FLOOR); - } - return fillAmountWei; + private static _createOptimizedZrxOrders(signedOrders: SignedOrder[]): MarketSellOrders { + _.forEach(signedOrders, signedOrder => { + signedOrder.makerAssetData = constants.NULL_BYTES; + signedOrder.takerAssetData = constants.NULL_BYTES; + }); + const params = formatters.createMarketSellOrders(signedOrders, constants.ZERO_AMOUNT); + return params; } - constructor(contractInstance: ForwarderContract, provider: Provider, zrxAddress: string) { + constructor(contractInstance: ForwarderContract, provider: Provider) { this._forwarderContract = contractInstance; this._web3Wrapper = new Web3Wrapper(provider); this._logDecoder = new LogDecoder(this._web3Wrapper, this._forwarderContract.address); - // this._web3Wrapper.abiDecoder.addABI(contractInstance.abi); - this._zrxAddress = zrxAddress; } - public async marketBuyTokensWithEthAsync( + public async marketSellOrdersWithEthAsync( orders: SignedOrder[], feeOrders: SignedOrder[], - makerTokenBuyAmount: BigNumber, txData: TxDataPayable, - opts: { feeProportion?: number; feeRecipient?: string } = {}, + opts: { feePercentage?: BigNumber; feeRecipient?: string } = {}, ): Promise { - const params = ForwarderWrapper._createOptimizedSellOrders(orders); - const feeParams = ForwarderWrapper._createOptimizedZRXSellOrders(feeOrders); - const feeProportion = _.isUndefined(opts.feeProportion) ? DEFAULT_FEE_PROPORTION : opts.feeProportion; + const params = ForwarderWrapper._createOptimizedOrders(orders); + const feeParams = ForwarderWrapper._createOptimizedZrxOrders(feeOrders); + const feePercentage = _.isUndefined(opts.feePercentage) ? constants.ZERO_AMOUNT : opts.feePercentage; const feeRecipient = _.isUndefined(opts.feeRecipient) ? constants.NULL_ADDRESS : opts.feeRecipient; - const txHash: string = await this._forwarderContract.marketBuyTokensWithEth.sendTransactionAsync( + const txHash = await this._forwarderContract.marketSellOrdersWithEth.sendTransactionAsync( params.orders, params.signatures, feeParams.orders, feeParams.signatures, - makerTokenBuyAmount, - feeProportion, + feePercentage, feeRecipient, txData, ); const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); return tx; } - public async marketSellEthForERC20Async( + public async marketBuyOrdersWithEthAsync( orders: SignedOrder[], feeOrders: SignedOrder[], + makerAssetFillAmount: BigNumber, txData: TxDataPayable, - opts: { feeProportion?: number; feeRecipient?: string } = {}, + opts: { feePercentage?: BigNumber; feeRecipient?: string } = {}, ): Promise { - const assetDataId = assetDataUtils.decodeAssetProxyId(orders[0].makerAssetData); - if (assetDataId !== AssetProxyId.ERC20) { - throw new Error('Asset type not supported by marketSellEthForERC20'); - } - const params = ForwarderWrapper._createOptimizedSellOrders(orders); - const feeParams = ForwarderWrapper._createOptimizedZRXSellOrders(feeOrders); - const feeProportion = _.isUndefined(opts.feeProportion) ? DEFAULT_FEE_PROPORTION : opts.feeProportion; + const params = ForwarderWrapper._createOptimizedOrders(orders); + const feeParams = ForwarderWrapper._createOptimizedZrxOrders(feeOrders); + const feePercentage = _.isUndefined(opts.feePercentage) ? constants.ZERO_AMOUNT : opts.feePercentage; const feeRecipient = _.isUndefined(opts.feeRecipient) ? constants.NULL_ADDRESS : opts.feeRecipient; - const txHash: string = await this._forwarderContract.marketSellEthForERC20.sendTransactionAsync( + const txHash = await this._forwarderContract.marketBuyOrdersWithEth.sendTransactionAsync( params.orders, + makerAssetFillAmount, params.signatures, feeParams.orders, feeParams.signatures, - feeProportion, + feePercentage, feeRecipient, txData, ); const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); return tx; } - public async calculateMarketBuyFillAmountWeiAsync( - orders: SignedOrder[], - feeOrders: SignedOrder[], - feeProportion: number, - makerAssetFillAmount: BigNumber, - ): Promise { - const assetProxyId = assetDataUtils.decodeAssetProxyId(orders[0].makerAssetData); - switch (assetProxyId) { - case AssetProxyId.ERC20: { - const fillAmountWei = this._calculateMarketBuyERC20FillAmountAsync( - orders, - feeOrders, - feeProportion, - makerAssetFillAmount, - ); - return fillAmountWei; - } - case AssetProxyId.ERC721: { - const fillAmountWei = await this._calculateMarketBuyERC721FillAmountAsync( - orders, - feeOrders, - feeProportion, - ); - return fillAmountWei; - } - default: - throw new Error(`Invalid Asset Proxy Id: ${assetProxyId}`); - } - } - private async _calculateMarketBuyERC20FillAmountAsync( - orders: SignedOrder[], - feeOrders: SignedOrder[], - feeProportion: number, - makerAssetFillAmount: BigNumber, - ): Promise { - const makerAssetData = assetDataUtils.decodeAssetDataOrThrow(orders[0].makerAssetData); - const makerAssetToken = makerAssetData.tokenAddress; - const params = formatters.createMarketBuyOrders(orders, makerAssetFillAmount); - - let fillAmountWei; - if (makerAssetToken === this._zrxAddress) { - // If buying ZRX we buy the tokens and fees from the ZRX order in one step - const expectedBuyFeeTokensFillResults = await this._forwarderContract.calculateMarketBuyZrxResults.callAsync( - params.orders, - makerAssetFillAmount, - ); - if (expectedBuyFeeTokensFillResults.makerAssetFilledAmount.lessThan(makerAssetFillAmount)) { - throw new Error(INSUFFICENT_ORDERS_FOR_MAKER_AMOUNT); - } - fillAmountWei = expectedBuyFeeTokensFillResults.takerAssetFilledAmount; - } else { - const expectedMarketBuyFillResults = await this._forwarderContract.calculateMarketBuyResults.callAsync( - params.orders, - makerAssetFillAmount, - ); - if (expectedMarketBuyFillResults.makerAssetFilledAmount.lessThan(makerAssetFillAmount)) { - throw new Error(INSUFFICENT_ORDERS_FOR_MAKER_AMOUNT); - } - fillAmountWei = expectedMarketBuyFillResults.takerAssetFilledAmount; - const expectedFeeAmount = expectedMarketBuyFillResults.takerFeePaid; - if (expectedFeeAmount.greaterThan(ZERO_AMOUNT)) { - const expectedFeeFillFillAmountWei = await this._calculateMarketBuyERC20FillAmountAsync( - feeOrders, - [], - DEFAULT_FEE_PROPORTION, - expectedFeeAmount, - ); - fillAmountWei = fillAmountWei.plus(expectedFeeFillFillAmountWei); - } - } - fillAmountWei = ForwarderWrapper._calculateAdditionalFeeProportionAmount(feeProportion, fillAmountWei); - return fillAmountWei; - } - private async _calculateMarketBuyERC721FillAmountAsync( - orders: SignedOrder[], - feeOrders: SignedOrder[], - feeProportion: number, - ): Promise { - // Total cost when buying ERC721 is the total cost of all ERC721 orders + any fee abstraction - let fillAmountWei = _.reduce( - orders, - (totalAmount: BigNumber, order: SignedOrder) => { - return totalAmount.plus(order.takerAssetAmount); - }, - ZERO_AMOUNT, - ); - const totalFees = _.reduce( - orders, - (totalAmount: BigNumber, order: SignedOrder) => { - return totalAmount.plus(order.takerFee); - }, - ZERO_AMOUNT, - ); - if (totalFees.greaterThan(ZERO_AMOUNT)) { - // Calculate the ZRX fee abstraction cost - const emptyFeeOrders: SignedOrder[] = []; - const expectedFeeAmountWei = await this._calculateMarketBuyERC20FillAmountAsync( - feeOrders, - emptyFeeOrders, - DEFAULT_FEE_PROPORTION, - totalFees, - ); - fillAmountWei = fillAmountWei.plus(expectedFeeAmountWei); - } - fillAmountWei = ForwarderWrapper._calculateAdditionalFeeProportionAmount(feeProportion, fillAmountWei); - return fillAmountWei; + public async withdrawERC20Async( + tokenAddress: string, + amount: BigNumber, + txData: TxDataPayable, + ): Promise { + const txHash = await this._forwarderContract.withdrawERC20.sendTransactionAsync(tokenAddress, amount, txData); + const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); + return tx; } } diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 19f2b1a23..b96bdb182 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -216,7 +216,7 @@ export enum RevertReason { Erc20InsufficientBalance = 'ERC20_INSUFFICIENT_BALANCE', Erc20InsufficientAllowance = 'ERC20_INSUFFICIENT_ALLOWANCE', UnacceptableThreshold = 'UNACCEPTABLE_THRESHOLD', - FeeProportionTooLarge = 'FEE_PROPORTION_TOO_LARGE', + FeePercentageTooLarge = 'FEE_PERCENTAGE_TOO_LARGE', ValueGreaterThanZero = 'VALUE_GREATER_THAN_ZERO', InvalidMsgValue = 'INVALID_MSG_VALUE', } -- cgit v1.2.3